2018-09-07(指针,多级指针,指针数组,指针数组字符串,结构体,结构体,枚举,变量修饰符)

  • 定义一个函数, 要求能够在函数中修改传入变量的值
    int num = 6;
    printf("调用之前:num = %i\n", num);
//    change(num);
    change(&num);
    printf("调用之后:num = %i\n", num);
    return 0;
}
//void change(int value){
// 结论: 如果函数的形参是数组或者是指针,
// 那么就可以在函数中修改外界传入变量的值
void change(int *value){ // 相当于 int *value = #
    *value = 888;
  • 需求: 要定定义一个函数, 在函数中交换传入变量的值
void main(){
    int num1=10,num2=20;
    chang(&num1,&num2);
    printf("num1=%i,num2=%i",num1,num2);

}
基本数据类型作为形参, 在函数内修改形参, 不会影响到函数外的实参,所以定义为指针变量,传进来地址
int chang(int *n1,int *n2){

    int mid=*n1;
    *n1=*n2;
    *n2=mid;
}
  • 需求: 要求定义一个函数, 可以同时返回两个变量的和,差,积,商
     需要大家知道的是: 在C语言中, 默认情况下一个函数只能返回一个值
     如果想让某一个函数同时返回多个值, 可以借助指针来实现
#include <stdio.h>
//int sum(int num1, int num2);
void test(int num1, int num2, int *res1, int *res2, int *res3, int *res4);
int main()
{
    int a = 10;
    int b = 20;
//    int res = sum(a, b);
//    printf("res = %i\n", res);

    int d, e, f, g;
    test(a, b, &d, &e, &f, &g);
    printf("和 = %i\n", d);
    printf("差 = %i\n", e);
    printf("积 = %i\n", f);
    printf("商 = %i\n", g);
    return 0;
//    printf("return 后面的语句\n");
}

/**先输出/**,然后按住Ctrl旁边的Fn+回车键,自动生成以下注释
 * @brief test 可以同时返回两个变量的和,差,积,商
 * @param num1 参与运算的第一个变量
 * @param num2 参与运算的第二个变量
 * @param res1 和
 * @param res2 差
 * @param res3 积
 * @param res4 商
 */
void test(int num1, int num2, int *res1, int *res2, int *res3, int *res4){
    *res1 = num1 + num2;
    *res2 = num1 - num2;
    *res3 = num1 * num2;
    *res4 = num1 / num2;
}

注意点:return后面的语句执行不到
int sum(int num1, int num2){
    // 注意点:
    // return的作用是用于结束当前函数
    // 只要函数执行到return, 就会立刻停止往下执行
    // 所以return 后面不可以编写任何语句, 因为执行不到
    return num1 + num2;
//    return num1 - num2;
//    return num1 * num2;
//    return num1 / num2;
}
*/
  • 多级指针
前面我们学习的指针我们称之为一级指针
     * 什么是多级指针
     * 指针中保存的又是其它指针的地址, 我们就称之为多级指针
     * 如何定义多级指针
     * 在要保存的指针变量的基础上加一颗星即可
     * 例如: int *p;  如果想保持指针变量p的地址, 只需在定义时多加一颗星即可 int **pp;
     *
     * 访问的规则:pp
     * 如果定义就如何访问, 例如: int *p; 访问 *p;  例如: int **pp; 访问 **pp;
     *
     * 定义指针的规律:
     * 1.将要指向的变量的定义拷贝过来
     * 2.再拷贝过来的代码的类型和变量名称中间加上一颗星
     * 3.修改变量名称
int num=6//int num;num=6;
int *p=&num;//int *p;p=&num;定义指针规律的意思就是把int num;拷贝过来,在int和num之间加*,在中间靠近谁都没关系,然后改变num的名称为p
int **p2=&p;
printf("&num = %p\n", &num); // &num =  0060FEAC
printf("p = %p\n", p); // p =  0060FEAC
printf("*pp = %p\n", *pp); // *pp = 0060FEAC

printf("&pp = %p\n", &pp); // &pp = 0060FEA4
 规律: 如果想通过多级指针获取某个变量的值, 那么是几级指针, 前面就写几颗星即可
 注意点: 在企业开发中, 最多二级指针, 三级顶天了, 四级没讲过
  • 指针和数组
 int ages[3] = {1, 3, 5};

    for(int i = 0; i < 3; i++){
        printf("ages[%i] = %i\n", i, ages[i]);
    }
    数组名称保存的就是数组占用内存最小的那个地址
    既然数组名称保存的就是地址, 而指针也是用于保存地址的, 所以指针也可以指向数组
    int *p = &ages;
    printf("ages = %p\n", ages); // ages = 0060FEA0
    printf("&ages = %p\n", &ages); // ages = 0060FEA0
    int *p = ages;// int *p = &ages
    printf("p = %p\n", p); // 0060FEA0
    结论: 如果利用指针保存数组的地址之后, 那么 p = ages = &ages;
  • 要求你写出三种访问数组元素的写法
     int ages[3] = {1, 3, 5};//第一种
     printf("ages[0] = %i\n", ages[0]);
     int *p = ages;//第二种
     printf("p[0] = %i\n", p[0]);
     printf("0[p] = %i\n", 0[p]);//第三种
  • 指针遍历数组
int ages[3] = {1, 3, 5};
    int *p = ages;
//    printf("*p = %i\n", *p); // 1
//    printf("*(p + 1) = %i\n", *(p + 1)); // 3
//    printf("*(p + 2) = %i\n", *(p + 2)); // 5

//    printf("*p = %i\n", *p++); // 1
//    printf("*p = %i\n", *p++); // 3
//    printf("*p = %i\n", *p); // 5
//    printf("*p = %i\n", *(--p)); // 3


    for(int i = 0; i < 3; i++){
//        printf("ages[%i] = %i\n", i, ages[i]);
        printf("ages[%i] = %i\n", i, *p++);
    }
  • 指针和字符串(用指针变量保存字符串)

指针和字符串
* 字符串的本质就是数组, 所以指针也可以指向字符串
* 正式因为如此, 所以定义字符串又多了一种方式

char str1[] = {'l', 'n', 'j', '\0'};
char str2[] = "lnj";//可以写成str2[i]
char *str4 = "lnj";//同样可以写成str2[i]
利用数组和指针定义字符串的区别:
     * 1. 存储的位置不同
     * 如果是通过数组定义的字符串, 那么存储在内存的栈中
     * 如果是通过指针定义的字符串, 那么存储在内存的常量区中
     *
     * 2.由于在内存中存储的位置不一样, 所以特性也不一样
     * 如果是通过数组定义的字符串, 我们是可以手动修改
     * 如果是通过指针定义的字符串, 我们不用手动修改
   char str[] = "lnj";
   char *str = "lnj";
   printf("str = %s\n", str);
   str2[1] = 'T';
   printf("str = %s\n", str);
     *
     * 3.由于在内存中存储的位置不一样, 所以特性也不一样
     * 如果是通过数组定义的字符串, 每次定义都会重新开辟存储空间
     * 如果是通过指针定义的字符串, 重复定义不会重新开辟存储空间

    char str1[] = "lnj";
    char str2[] = "lnj";
    printf("str1 = %p\n", str1); // 地址不一样
    printf("str2 = %p\n", str2);

    char *str1 = "lnj";
    char *str2 = "lnj";
    printf("str1 = %p\n", str1); // 地址一样
    printf("str2 = %p\n", str2);
  • 接收和打印,字符串数组和指针的区别
 1.接收字符串的时候, 只能通过字符数组, 不能通过字符指针
char str[10];
char *str;//程序编译是不报错,运行时出现问题,这样定义接收不到
scanf("%s", str);
rintf("str = %s\n", str);
 2.如果函数中返回的字符串是通过数组创建的, 那么外界无法获取
    如果函数中返回的字符串是通过指针创建的, 那么外界可以获取
    char *res = demo();
    printf("res = %s\n", res);
char* demo(){//返回值为指针类型,所以有星号
    char str[] = "lnj";//函数外面接收不到
    char *str = "lnj"
    return str;//str保存的是常量区lnj\0的地址

/ 注意点: 学习了指针之后, 建议将过去形参的数组类型修改为指针类型
int ages[3] = {1, 3, 5};
     test(ages);
     printf("ages[1] = %i\n", ages[1]);

//void test(int nums[]){
void test(int *nums){//由于test(ages)传进来的就是地址,用指针更形象
 nums[1] = 6;
  • 字符串数组
字符串数组
    // 字符串就是一个数组, 所以字符串数组就是一个二维数组

   char str[][] = {//字符串数组表示方法
       "lnj",
       "abc",
       "def"
    };

    // 字符串数组的第二种格式
    char *str[] = {
        "lnj",
        "abc",
        "def"
    };
  • 练习
    1.实现strlen()函数
char *str = "lnj666";//此处注意字符串为char类型,形参处也为char类型
//    char str[10] = {'l', 'n', 'j', '\0'};
    // 注意点: strlen计算的是保存了多少个字符串
    // 计算规则, 就是返回字符串中\0之前有多少个字符
int res = myStrlen(str);
    printf("res = %i\n", res);
    return 0;
}
int myStrlen(char *str){
    // 1.定义变量记录当前有多少个
    int count = 0;
    while(*str++){
        count++;
    }
//    while(*str++ != '\0'){   方法二
//        count++;
//    }

//    while(str[count] != '\0'){     方法三
//        count++;
//    }
   return count;
}

2.实现strcat()函数

注意点: 第一个参数必须足够大
 //char *str1 = "lnj";  // 不能用于拼接,因为不能看出第一个字符空间大小
//    char *str2 = "it666";
    char str1[10] = "lnj";
    char str2[10] = "it666";
    myStrcat(str1, str2);
    printf("str1 = %s\n", str1);
    return 0;
}
void myStrcat(char *str1, char *str2){
方法一:
    while(*str1){
         str1++;
    }
 while(*str2){
        *str1 = *str2;
        str1++;
        str2++;
    }
*str1='\0';
}

方法二:
 // 1.拿到第一个数组\0的位置
    int index = 0;
    while(str1[index] != '\0'){
        index++;
    }
    // 2.将第二个数组添加到第一个数组末尾
    int count = 0;
    while(str2[count] != '\0'){
        str1[index] = str2[count];
        count++;
        index++;
    }

3.实现strcpy()

同样不能用指针定义字符串数组,
int main()
{
      char str1[9] = "lnj";
      char str2[10] = "it";
//    strcpy(str1, str2);
    myStrcpy(str1, str2);
    printf("str1 = %s\n", str1);
    return 0;
}
void myStrcpy(char *str1, char *str2){
    while(*str2){
        *str1 = *str2;
        str1++;
        str2++;
    }
    *str1 = '\0';
}
/*
void myStrcpy(char *str1, char *str2){
    int index = 0;
    while(str2[index] != '\0'){
        str1[index] = str2[index];
        index++;
    }
    str1[index] = '\0';
}
*/

4.实现strcmp()

int main()
{
    // strcmp
    char str1[9] = "125";
    char str2[10] = "124";
    // 两个字符串相同返回0
    // 第一个参数小于第二个参数 -1 负数
    // 第一个参数大于第二个参数 1 正数
    // 如果前面的内容都相同, 第一个参数的个数小于第二个参数, -1 负数
    // 如果前面的内容都相同, 第一个参数的个数大于第二个参数, 1 正数
//    int res = strcmp(str1, str2);
    int res = myStrcmp(str1, str2);
    printf("res = %i\n", res);
    return 0;
}
int myStrcmp(char *str1, char *str2){

//    while(*str1 != '\0' || *str2 != '\0'){
     while(*str1 || *str2){
        if(*str1 > *str2){
            return 1;
        }else if(*str1 < *str2){
            return -1;
        }
        str1++;
        str2++;
    }
    return 0;
}

/*方法二:
int myStrcmp(char *str1, char *str2){
    int index = 0; // 3
    //      \0                        \0
    while(str1[index] != '\0' || str2[index] !='\0'){
        //      3           3
        if(str1[index] > str2[index]){
            return 1;
        }else if(str1[index] < str2[index]){
            return -1;
        }
        index++;
    }
    return 0;
}
*/

  • 指向函数的指针

    • 指针变量的作用: 专门用于保存地址
      * 指向函数的指针
      * 计算机也会给函数分配存储空间, 既然函数会分配内存空间,
      * 所以函数也有自己的地址, 所以指针变量也可以保存函数的地址
      *
      * 经过前面的学习, 我们知道数组名保存的就是数组的地址
      * 函数和数组很像, 函数名中保存的就是函数的地址
      *
      * 如何定义指针变量?
      * 1.将需要保存变量的定义拷贝过来
      * 2.在数据类型和变量名称中间添加一个*
      * 3.修改变量名称
      *
      * 如何定义保存函数的指针变量
      * 1.将函数的什么拷贝过来
      * 2.在函数返回值和函数名称总监添加一个*
      * 3.修改函数名称
      * 4.注意点: 需要将*和变量名称用括号括起来
      *
      * 我们说过指向函数的指针和指向数组的指针很像
      * 如果是数组, 我们可以直接将数组名称赋值给一个指针变量
      * 如果是函数, 我们也可以直接将函数名称赋值给一个指针变量
      *
      * 如果一个指针指向了数组, 那么访问数组就有了三种方式
      * 数组名称[索引];
      * 指针变量名称[索引]
      * *(指针编码名称 + 索引)
      *
      * 如果一个指针指向了函数, 那么访问方式也有多种方式
      * 函数名称();
      * ( *指针变量名称)();
      * 指针变量名称();

     int num;
     int *p;
     p = &num;

    int ages[3] = {1, 3, 5};
     int *p;
     p = ages;
    printf("ages[1] = %i\n", ages[2]);//三种方式访问数组
    printf("p[1] = %i\n", p[2]);
    printf("p[1] = %i\n", *(p+2));


    test(); // 第一种方式

    void (*funcP)();
    funcP = &test;//或者  funcP = test;
    (*funcP)(); // 第二种方式

    void (*funcP)();
    funcP = &test;//或者  funcP = test;
    funcP(); // 第三种方式
void test(){
    printf("test\n");
}
  • 练习:定义指针指向这几个函数
    void (*p1)();
    p1 = say;
    p1();

    void (*p2)(int, int);
    p2 = sum;
    p2(10, 20);


    int (*p3)(int a, int b);
    p3 = minus;
    int res = p3(10, 20);
    printf("res = %i\n", res);

    char* (*p4)();
    p4 = test;
    char* res2 = p4();
    printf("res2 = %s\n", res2);
    return 0;
}
void say(){
    printf("Hello World\n");
}
void sum(int a, int b){
    printf("res = %i\n", a + b);
}
int minus(int a, int b){
    return a - b;
}
char* test(){
    char* name = "lnj";
    return name;
}
  • 要求一个函数既可以计算两个变量的和, 也可以计算两个变量的差

//    int res1 = sum(10, 20);
//    printf("res1 = %i\n", res1);

//    int res2 = minus(10, 20);
//    printf("res2 = %i\n", res2);

    int res3 = test(10, 20, sum);//把sum地址传给test
    printf("res3 = %i\n", res3);

    int res4 = test(10, 20, minus);
    printf("res4 = %i\n", res4);
    return 0;
}
 注意点: 指向函数的指针,作为函数的形参时, 指针变量的名称, 就是形参的名称
// 如果指向函数的指针作为函数的参数, 那么这个可称之为回调函数
// 这里相当于, 给test函数传入了一个sum函数或者minus函数
// 然后又在test函数中调用了sum函数或者minus函
int test(int num1, int num2, int (*funP)(int, int)){
    return funP(num1, num2);//把num1和num2的值传给sum函数

int sum(int num1, int num2){
    return num1 + num2;
}
int minus(int num1, int num2){
    return num1 - num2;
}
  • 作业
    1.要求从键盘输入一个字符串, 并且字符串中可以出现空格
  • 2.将用户输入的字符串, 单词的首字母变成大写, 单词用空格划分
  • hello world; --> Hello World;
  • 3.将用户输入的字符串, 单词的首字母编程小写, 单词用空格划分
  • Hello World; --> hello world;
  • 4.要求定义一个函数, 既可以处理将首字母变为大写, 也可以处理将首字母变为小写
  • 需要用到指向函数的指针

  • 结构体
    /*
     * 什么是结构体?
     * 结构体时构造类型的一种
     *
     * 构造类型前面我们已经学习过了数组:
     * 数组的作用是用于存储一组相`同类型`的数据
     * 结构体的作用是用于存储一组`不同类型`的数据
     *
     * 保存一个人的信息
     * 姓名/年龄/身高 ...
     * char *
     * int
     * double
     *
     * 如何定义结构体变量
     * 1.先定义结构体类型
     * 2.通过结构体的类型定义结构体变量
     *
     * 如何定义结构体类型?
     * struct 结构体类型名称{
     *   数据类型 属性名称;
     *   数据类型 属性名称;
     *   ... ...
     * };
     *
     * 如何定义结构体变量
     * struct 结构体类型名称 变量名称;
     *
     * 如何访问结构体的属性
     * 结构体变量名称.结构体属性名称;
     */
    // 1.定义结构体类型
    struct Person{
        char *name; // name我们称之为结构体的属性名称
        int age; // age也称之为结构体的属性名
        double height; // height也称之为结构体的属性名称
    };
    // 2.定义结构体变量
    struct Person p;

    // 3.使用结构体变量
//    int ages[3] = {1, 3, 5};
//    ages[0] = 1;
    // 格式: 结构体变量名称.结构体属性名称
    p.name = "lnj";
    p.age = 35;
    p.height = 1.9;
    printf("name = %s\n", p.name);
    printf("age = %i\n", p.age);
    printf("height = %lf\n", p.height);
    return 0;
}
  • 结构体变量初始化的几种方式
1.定义结构体类型
    struct Dog{
        char *name;
        int age;
        double height;
2.1先定义后初始化
//    struct Dog dd;
//    dd.name = "ww";
//    dd.age = 1;
//    dd.height = 1.5;
2.2定义的同时初始化
//    struct Dog dd = {"ww", 1, 1.5};
注意点: 如果在定义的同时初始化, 那么初始化的顺序必须和结构体类型中的顺序一致
struct Dog dd = {.age = 1, .name = "ww", .height = 1.5};//也可以指定初始化,可以部分初始化.
3.特殊的初始化方式
   数组只能在定义的同时完全初始化, 不能先定义再完全初始化
   但是结构体既可以在定义的同时完全初始化, 也可以先定义再完全初始

//    int ages[3] = {1, 3, 5};
//    int ages[3];
//    ages = {1, 3, 5};//数组不可以

 // 企业开发不推荐这样编写
    struct Dog{
        char *name;
        int age;
        double height;
    };
     struct Dog dd;
     dd = (struct Dog){"ww", 1, 1.5};//强制转换
     printf("name = %s\n", dd.name);
     printf("name = %i\n", dd.age);
     printf("name = %lf\n", dd.height);
  • 定义结构体变量的方式
       1.先定义结构体类型, 再定义结构体变量
    /*
    struct Person{
        char *name;
        int age;
        double height;
    };
    struct Person p1;
    struct Person p11;
    */

     2.定义结构体类型的同时定义结构体变量
    /*
    struct Person{
        char *name;
        int age;
        double height;
    } p2;
    p2.name = "lnj";
    printf("name = %s\n", p2.name);
    struct Person p22;
    */
   3.定义结构体类型的同时省略结构体名称, 同时定义结构体变量
    // 匿名结构体
    // 特点: 结构体类型只能使用一次
    struct{
        char *name;
        int age;
        double height;
    } p3;
    p3.name = "it666";
    printf("name = %s\n", p3.name);
    return 0;
  • 结构体作用域(和变量相同)
  • 结构体数组及初始化
  struct Person{
        char *name;
        int age;
        double height;
    };
方法一:先定义在初始化
 //    struct Person p1 = {"lnj", 35, 1.90};
//    struct Person p2 = {"zs", 22, 1.2};
//    struct Person p3 = {"ls", 33, 1.4};
//    struct Person p4 = {"ww", 56, 1.8};
   数据类型 数组名称[元素个数];
    struct Person ps[4];
     ps[0] = p1;//ps[0] = {"lnj", 35, 1.90};下面也如此
     ps[1] = p2;
     ps[2] = p3;
     ps[3] = p4;
方法二:定义同时初始化
 struct Person ps[4] ={
         {"lnj", 35, 1.90},
         {"zs", 22, 1.2},
         {"ls", 33, 1.4},
         {"ww", 56, 1.8},
     };
  • 结构体内存分析

注意点:
* 给整个结构体变量分配存储空间和数组一样, 从内存地址比较大的开始分配
* 给结构体变量中的属性分配存储空间也和数组一样, 从所占用内存地址比较小的开始分配
*
* 注意点:
* 和数组不同的是, 数组名保存的就是数组首元素的地址
* 而结构体变量名, 保存的不是结构体首属性的地址

 struct Person{
        int age;
        int score;
    };
    struct Person p;
    printf("p = %p\n", p); // p = 00000077
    printf("&p = %p\n", &p); // &p = 0060FEA8
    printf("&p.age = %p\n", &p.age); // &p.age = 0060FEA8
    printf("&p.score = %p\n", &p.score); // &p.score = 0060FEAC
    //结构体在分配内存的时候, 会做一个内存对齐的操作
    // 会先获取所有属性中占用内存最大的属性的字节
    // 然后再开辟最大属性字节的内存给第一个属性, 如果分配给第一个属性之后还能继续分配给第二个属性, 那么就继续分配
    // 如果分配给第一个属性之后, 剩余的内存不够分配给第二个属性了, 那么会再次开辟最大属性直接的内存, 再次分配
    //以此类推
  • 结构体指针

结构体指针
* 因为结构体变量也会分配内存空间, 所以结构体变量也有内存地址, 所以也可以使用指针保存结构体变量的地址
*
* 规律:
* 定义指向结构体变量的指针的套路和过去定义指向普通变量的一样
*
* 如果指针指向了一个结构体变量, 那么访问结构体变量的属性就有3种方式
* 结构体变量名称.属性名称;
* (*结构体指针变量名称).属性名称;
* 结构体指针变量名称->属性名称;

struct Person{
        char *name;
        int age;
        double height;
    };
    struct Person per = {"lnj", 35, 1.9};//变量名为per
    struct Person *p;//定义一个指针变量p,struct Person为结构体类型
    p = &per;
   printf("per.name = %s\n", per.name);
    printf("per.name = %s\n", (*p).name);
    printf("per.name = %s\n", p->name);
  • 结构体的嵌套(提高代码的复用性)
struct Person{
        char *name;
        int age;

        // 出生年月
        int year;
        int month;
        int day;

        // 死亡日期
        int year2;
        int month2;
        int day2;

        // 读幼儿园日期
        // 读小学日期
        // 读中学日期
所以可以把年月日写成一个结构体
// 1.定义了一个日期的结构体类型
    struct Date{
        int year;
        int month;
        int day;
    };
    // 2.定义一个人的结构体类型
    struct Person{
        char *name;
        int age;
        struct Date birthday;
    };
    struct Person p = {"lnj", 35, {2020, 12, 12}};

    printf("name = %s\n", p.name);
    printf("name = %i\n", p.age);
    printf("name = %i\n", p.birthday.year);
    printf("name = %i\n", p.birthday.month);
    printf("name = %i\n", p.birthday.day);
  • 结构体和函数(强调结构体作用域和变量相同)

1.虽然结构体是构造类型, 但是结构体变量之间的赋值
* 和基本数据类型赋值一样, 是拷贝
2. 注意点: 定义结构体类型不会分配存储空间
* 只有定义结构体变量才会分配存储空间

struct Person{
        char *name;
        int age;
    };
    struct Person p1 = {"lnj", 35};
    struct Person p2;
    p2 = p1;
    p2.name = "zs";
    printf("p1.name = %s\n", p1.name); // lnj
    printf("p2.name = %s\n", p2.name); //  zs


把p1的name更改写在函数中,更改后内存会释放掉,最终name值不变
struct Person p1 = {"lnj", 35};
    printf("p1.name = %s\n", p1.name); // lnj
    test(p1);
    printf("p1.name = %s\n", p1.name); // lnj
    return 0;
}
void test(struct Person per){
    per.name = "zs";
  }
  • 共用体(企业开发中用的比较少)

共用体
*
* 共用体的格式:
* union 共用体名称{
* 数据类型 属性名称;
* 数据类型 属性名称;
* ... ...
* }
* 共用体定义的格式和结构体只有关键字不一样, 结构体用struct,共用体用union
*
* 共用体特点:
* 结构体的每个属性都会占用一块单独的内存空间, 而共用体所有的属性都共用同一块存储空间(同样先分配占用属性最大的字节数)
* 只要其中一个属性发生了改变, 其它的属性都会受到影响
*
* 应用场景:
* 同一个变量, 在不同的时刻,需要表示不同类型数据的时候, 我们就可以使用共用体

     union Test{
        int age;
        char ch;
    };
    union Test t;
    printf("sizeof(p) = %i\n", sizeof(t));

    t.age = 33;
    printf("t.age = %i\n", t.age); // 33
    t.ch = 'a';
    printf("t.ch = %c\n", t.ch); // a
    printf("t.age = %i\n", t.age); // 97,,此时原来age的空间已经给ch用了
  • 枚举(开发中经常用到)

枚举?
* 枚举用于提升代码的阅读性, 一般用于表示几个固定的值
* 所以还有一个名称, 叫做枚举常量
*
* 如果某些变量的取值是固定的, 那么就可以考虑使用枚举来实现
*
* 枚举的格式:
* enum 枚举类型名称{
* 取值1,
* 取值2,
* };
* 注意点: 和结构体,共用体不同, 枚举是用逗号隔开
*
* 规范:
* 枚举的取值一般以K开头,后面跟上枚举类型名称, 后面再跟上表达的含义
* K代表这是一个常量
* 枚举类型名称, 主要是为了有多个枚举的时候, 方便区分
* 含义, 你想表达的意思
*
* 枚举的取值:
* 默认情况下从0开是取值, 依次递增
* 也可以手动指定从几开始, 依次递增,例如KGenderMale = 9,从9开始递增

enum Gender{
        KGenderMale , //打印输出为0,可写成male,但是 KGenderMale表达更清晰,编写的时候会提示输出
        KGenderFemale, // 1,可写成female
                       // 2 ... ...
    };
 struct Person{
        char *name; // 姓名
        int age; // 年龄
        enum Gender gender; // 性别
    };
    struct Person p1;
    p1.name = "lnj";
    p1.age = 58;
    p1.gender = KGenderFemale;

    struct Person p2;
    p2.name = "周芷若";
    p2.age = 88;
    p2.gender = KGenderFemale;
  • 枚举的作用域(和结构体类型的作用域一样, 和变量的作用域一样)
  • 枚举的定义方式(和结构体相同)
   2.1先定义枚举类型, 再定义枚举变量
     enum Gender{
        KGenderMale,
       KGenderFemale,
     };
    enum Gender g1;

    2.2定义枚举类型的同时, 定义枚举变量
   enum Gender{
        KGenderMale,
        KGenderFemale,
     } g2;

    2.3定义枚举类型的同时,定义枚举变量 ,并且省略枚举类型名称
    enum{
        KGenderMale,
        KGenderFemale,
    } g3;

回顾局部变量和全局变量
* 局部变量:
* 概念: 定义在{}中,函数中,形参都是局部变量
* 作用域: 从定义的那一行开始, 直到遇到}结束或者遇到return为止
* 存储的位置: 局部变量会存储在内存的栈区中, 会随着定义变量的代码执行分配存储空间, 会随着作用域的结束自动释放
* 特点:
* 相同作用域类, 不能出现同名的局部变量
* 如果不同作用域内有相同名称的变量, 那么在访问时, 内部的会覆盖外部的(就近原则)
*
* 全局变量:
* 概念: 定义在{}外或者函数外的变量, 都是全局变量
* 作用域: 从定义的那一行开始, 直到文件末尾
* 存储的位置: 全局变量会存储在内存的静态区中, 会随着程序的启动分配存储空间, 随着程序的结束释放存储空间
* 特点:
* 如果有多个同名的全局变量, 那么也只会分配一次存储空间, 多个同名的全局变量共用同一块存储空间

  • 变量修饰符(auto ,register,extern,static)
    1.修饰局部变量(auto ,register)
  • auto和register都是用于修饰局部变量的
    auto int num = 9;
    register int num = 9;
    * 它们年的作用是修饰编程的存储特性
    * auto: 特点就是告诉编译器, 局部变量离开作用域自动释放
    * 默认情况下所有局部变量都是auto的, 所以这一句废话, 所以了解--> 忘记 / 即可
    *
    * register: 特点就是告诉编译器, 将局部变量存储到CPU寄存器中
    * 好处就是访问的速度会更快, 但是在不同平台,不同编译器下, 会做不同的优化,寄存器存储空间非常有限,不一定能够存储进去, 所以还是一句废话, 所以了解 -->

2.static

2.1static对局部变量的作用
如果利用static修饰局部变量, 那么会将局部变量的存储区域从栈区移动到静态区
* 静态区只有程序结束才会释放
void calculate(int r){
// PI使用的概率非常大, 如果是一个局部变量的话, 每次调用都会重新开辟存储空间, 这样性能不好
// 如果PI是static的变量, 那么只会开辟一次, 那么性能就会好很多
static double pi = 3.1415926;
return r * r * pi;
}
2.2 static对全局变量的作用
* 定义一个内部的全局变量,
* 1.该变量只能在定义的文件中使用, 不能在其它文件中使用,例:在dashen.c文件中
* 2.并且该变量会独占一块内存空间
*
* 全局变量的特性:
* 可以定义多个同名的全局变量, 多个同名的全局变量共享一块内存空间
* 哪怕不是同一个文件中的同名全局变量, 也会共享同一块内存空间
* 问题:
* 这样是不是会导致数据混乱,因此引入static,在其他文件中无法使用
*
* 注意点:
* 局部变量如果没有初始化, 里面存储的是垃圾数据
* 全局变量如果没有初始会, 系统会自动初始化为0
2.3 static修饰函数
*代表这事一个内部函数, 只能在当前文件中使用
* 如果一些内部函数不想提供给外界使用, 那么就可以给函数添加一个static
*static必须写到函数的实现中才有效, 不能写到函数的声明中
* 并且如果一个函数已经被声明为static的了, 那么在.h文件中就不要编写该函数的声明了

3.extern

  • extern对局部变量的作用
    * extern用于声明一个变量, 声明变量并不会开辟存储空间
    * extern一般用于全局变量, 至今没见过有人用extern来修饰局部变量
    *
    *
    * extern对全局变量的作用
    * extern用于声明一个变量, 声明变量并不会开辟存储空间
    * exter只能用于全局变量, 不能用于局部变量
    *
    * 原因:
    * 局部变量, 只有执行到那一行代码才会分配存储空间, 所以哪怕声明了 ,但是在使用时还是没有分配, 所以还是不能存储数据
    * 全局变量, 会随着程序的启动分配存储空间, 所以只要声明了, 使用时已经分配好了存储空间, 一定能够使用, 一定能够存储数据
    *
    *
    如果利用extern修饰函数, 代表这是一个外部函数, 其它文件中也可以使用
    * 注意点: 默认情况下所有函数都是外部函数, 所有的函数都可以在其它文件中访问, 所以extern是一个废物
    extern必须写到函数的实现中才有效, 不能写到函数的声明中

{extern int num;
    num = 998;
printf("num = %i\n", num);
    return 0;
}  int num;
  • 企业开发中大部分都是多人开发, 多人开发就是多个人一起写一个项目,所以在企业开发中, 都是多人同时操作多个不同的文件

例如: 现在有两个员工
* 一个是菜鸟, 一个是大神
* 调用大神写好的代码
* 编写主要的业务逻辑代码
*
在企业开发中如何进行多人开发
* 一般情况下会将(函数的实现), 编写到.c文件中, 同时会将.c文件中需要暴露给外界使用的方式名称的声明写到.h文件中
* 为什么编写了.c文件还需要编写一个.h文件, 原因很简单, (函数的实现)是你编写的, 那么函数的作用,形参你最了解, 所以应该由你来编写

注意:
* 在企业开发中, 其它人不需要关系函数具体是如何实现的, 只需要关心如何使用这个函数即可
* 所以(函数的实现)和声明都应该让同一个人来完成
*
* main函数中一般为主要业务逻辑,不能有太多冗余代码
* #include的作用:
* 将后面指定文件中的内容拷贝到当前文件中
* <>从系统的环境变量中去拷贝, 一般情况下只有用到系统函数才使用<>
* " "从指定的路径中去拷贝, 一般情况下使用同事/自己编写的.h文件都用""

    #include "ds.h" // 导入大神编写的.h文件,例:#include "D:\WWW\ds.h"
    本质就是将大神编写的.h文件中的代码拷贝过来
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容