• 嗨翻C语言笔记(一)


    对自己狠一点,逼自己努力,总有一天你会感谢今天的自己!

    image

    C语言不支持现成的字符串, 只能用数组表示. 
    
    & (and)运算, 即两个数的每个二进制位都进行比较, 对等位均为1时为1, 否则为0. 比如在计算机网咯笔记中有 ip和子网掩码对比获取子网 的印象过程中:
    

    image

    C 的数据类型
        浮点型: float, double
        整数型: char, int, short,long
                [Cchar以字符编码保存,比如a保存为 65 ]
    
    所以 6 & 4 的结果是4, 这是怎么求得的呢? 
    6 转成二进制是          110
    4 转成二进制是          100
    两个数求 & 后的值是  100   转换成十进制就是4, 因此结果就是4. 
    
    printf()  //用于格式化输出
    %s : 字符串
    %i  : 整数
    %p : 将地址以10禁止的格式输出
    
    
    在C中指针的长度占用8个字节. (32位占4个字节,64位系统占8个字节)
    
    * 运算符 和 &运算符的区别:
    & 	
    @1. 后接变量名,表取址运算符,用于获取该变量的内存地址 
    @2. 做二进制运算表 and 运算,用于返回两个比较数二进制对等位上同时为真的位, 比如 6 & 4 返回 4. 
    
    * 
    @1. 声明变量时加它仅表指针类型
      	 @2.使用时在变量前加它,表取值运算符,用于获取该内存地址对应的值
    
    因此
    int *p;        //声明时前面带 * , 进表示他是指针类型
    *p = 5;      //该指针p没有任何引用地址,就妄图赋值,报错的, 它是野指针, 必须先有内存地址后才能赋值. 
    int a = 10;
    p = &a;    //&a是取址,返回给p变量的是一个地址值, 
    *p = 14;  //单个 *p 表取值, 整个表达式的意思是先取值然后给赋值14
    
    再来深入理解下面的例子:
    	char a[] = "hello";  //实质上等于 char a[] = {'h', 'e', 'l', 'l', 'o'};
    	char *p;
    
    	p = &a;     				 //p获得了字符串的第一个元素的地址 
    	printf("p=%p
    ", p);
    	printf("p+1=%p 
    ", (p+1) );   		 //打印第二个元素的地址
    	printf("*(p+1)=%c 
    ", *(p+1) );		//打印第二个元素的值, %c表字符, %s表字符串,不要用错. 
    
    输出为:
    p	= 0x7fff5a563786
    p+1	= 0x7fff5a563787    86,87结尾充分说明一个字符占用一个字节
    *(p+1)=e
    
    上面字符串地址 p 和 p+1 只差一位,那是因为它是字符, 如果是整型,那么这个地址值就要差4位了, 看下面:
    int a[] = {1,2,3,4};
    int *p = NULL;
    	p = &a;    			//p获得了字符串的第一个元素的地址
    	printf("p=%p
    ", p);			//打印第一个元素的地址
    	printf("p+1=%p 
    ", (p+1) ); 		//打印第二个元素的地址
    	printf("*(p+1)=%d 
    ", *(p+1) );    //打印第二个元素的值
    
    看输出:
    p    =0x7fff5fad8770
    p+1=0x7fff5fad8774
    *(p+1)=2
    
    
    
    存储区的结构:
    栈区			   	--局部变量
    堆区
    全局量区			--所有函数外,包括main函数
    常量区域			
    代码段区域
    
    
    14:38 冷水
    
    vs 3. sizeof() 可用于获取变量的占用长度或类型长度
    @1. 类型:
    sizeof(int) ; //返回4, 表整型占用4个字节
    @2.占用字节:
    	char b[] = "hello world";
    	printf("%d
    ",sizeof(b) ); //使用数组变量名, 返回12,其中加空格是11个,最后一个 o 数组结束符
    
    @3. 指针类型
    	check(b);   //输出8, 表指针的长度是八个字节
    void check(char msg[]) { 
     //当参数传递的时候,用sizeof()把数组当成了第一个元素的指针地址[指针类型],因此输出的是指针的长度
    		printf("sizeof value:%d", sizeof(msg));
    }
    
    vs 4. 计算机会为字符串的每一个字符(包括空格)以及结束符0 在栈上分配空间,并把首字符的地址和变量名关联起来, 只要出现这个数组变量名, 计算机就会把他替换成字符串首字符的地址,因此数组变量就好比一个指针. 
    printf("%s
    ", b ); //输出 "hello world";      当字符串
    printf("%p 
    ", b); //输出 0x7fff5a63878   当地址
    
    
    vs 5. 特殊的变量字符串 - 在C语言中字符串其实是char数组
    char *cards = "jqk";				//"jqk"是存放在常量区的,只读
    printf("cards=%p
    ", cards);			//cards 获得了常量字符串的第一个元素的指针
    printf("cards=%p
    ", cards+1);		//输出第二个元素地址
    printf("cards=%p
    ", cards+2);		//输出第3个元素地址
    printf("*cards=%c
    ", *cards);		//输出第1个字符元素
    printf("*cards=%c
    ", *(cards+1));	//输出第2个字符元素
    printf("*cards=%c
    ", *(cards+2));	//输出第3个字符元素
    
    输出:
    cards	=0x10475ef30
    cards+1	=0x10475ef31
    cards+2	=0x10475ef32
    *cards	=j
    *cards+1=q
    *cards+2=k
    
    如果妄图修改 *cards 的值都会报错的, 因为它是常量的, 要修改只能把字符串转成数组, 那么它就存储在全局变量区或栈区,就可以进行修改了,如下:
    
    char cards2[] = "hello";    //字符串在栈区是当做数组存储的 相当于 char cards2[] = {'h','e','l','l','o'}
    *(cards2 + 1) = 'g';
    printf("cards2=%s 
    ", cards2);
    
    //上面的切记写成  char *cards2[] = "hello";   //没有这样的, 指针是一个定长地址值,一般表某类型的第一个地址, 你用 *cards[] 表示是一个指针的数组,指着N个元素,  而你的值却是一个字符串, 除非你写成  char *cards2[] = {"hello", "world"};
    因此 char *cards2 = "hello"; 相当于 char *cards2 = {'h','e','l','l','o'}	
     因此 *cards2 表示第一个字符 'e'
    
    char *cards3[] = ["hello"] 是不一样的.
     而 *cards3 表示的是第一个字符串 "hello" 
    
     使用 cards2时具体看语义是打印整个数组还是打印地址值, 如下:
    printf("cards2=%p", cards2);    //输出 0x7fff5f06e75a,  %p 表要输出的是 栈区内存地址值
    printf("cards2=%s", cards2);    //输出 "hello",  %s 表要输出的是字符串字面值
    
    
    标准库:
    stdio.h 提供了标准输入/输出函数, printf(), scanf(), fgets()...
    string.h 提供了字符串的各种操作函数. 
    
    vs 7. 指针运算术
    例如编写一个翻转某字符串的函数:
    
    void print_reverse(char *s) {
    size_t len = strlen(s);
    char *t = (s + len) -1 ;   //获取该字符串的最大的指针地址
    while ( t >= s ) {
    printf("%c
    ",  *t );
    t = t - 1;
    }
    
    }
    
    
    vs 8. 数组指针
    char *arr[4] = {"hello", "world", "good", "night"};
    其中 arr 表示第一个元素的地址, *arr 表示第一个元素的取值即"hello"
    那么 *(arr + 1) 的取值就是"world", 以此类推 ...
    *(p+i) 的写法等价于 p[i] 
    那么 arr[1] 等价于写 *(arr +1) , 于是 arr[1] 也等于 "world"
    
    vs 9. 
    ./geo2json < gpsdata.csv > output.json
    在屏幕上等待用户输入
    在没使用导入文件前, 每条数据都要在屏幕上使用 scanf("%f, %f, %79[^
    ]", &latitude, &longitude, info) 函数等待用户输入,  
    其实屏幕输入和  "< gpsdata.csv" 导入文件是一样的, 都是标准输入stdin, 因此可用后者取代前者. 
    
    前半部分获取标准输入到程序中去, 然后程序的标准输出到output.json 文件去
    一般情况下使用 
    printf() 默认会把文本发送到标准输出stdout 去, 假如你要实现正常的发送到标准输出, 异常的发送到标准错误stderr去, 该咋办???
    "<" 标准输入 stdin
     ">" 的作用是重定向标准输出 stdout
     "2 > " 是重定向标准错误.  stderr 
    
    
    假如你原来的
    printf( "invalid latitude. %f
    ", latitude);  该类错误信息不用弄到标准输出去, 否则会被捕获到  > output.json去的, 你该咋办???
    使用 fprintf(stderr, "invalid latitude. %f
    ", latitude);   //表示打印到标准错误去, 因此不会被 "> output.json" 进去, 而是输出到屏幕上. 
    
    fprintf();  第一个参数用于指定 stdin, stdout, stderr中的一种类型. 
    
    

    vs 10. 根据用户输入的参数来刷选, 并把数据写入指定的文件中去

    比如命令行执行 ./categorize mermaid mermaid.csv Elvis elvis.csv the_rest.csv
    • mermaid : 第一个要过滤 argv[0]
    • mermaid.csv: 第一个要过滤的数据mermaid 写入这个文件 argv[2]
    • Elivis 第二个过滤argv[3], elvis.csv第二个要写入的文件 argv[4]
    • the_rest.csv 其余的数据保存在这里. argv[5]
    • ./categorize hello hello.csv good good.csv today.csv
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {
    	char line[80];
    	if(argc != 6) {
    		fprintf(stderr, "you need to give 5 arguments!
    ");
    		return 1;
    	}
    	//读取的文件
    	FILE *in = fopen("in.csv", "r");
    	//将要写入的三个文件
    	FILE *file1 = fopen(argv[2], "w");
    	FILE *file2 = fopen(argv[4], "w");
    	FILE *file3 = fopen(argv[5], "w");
    
    	while (fscanf(in, "%79[^
    ]
    ", line) ==1 ) {
    		if (strstr(line, argv[1]) ) //搜索到这个字符就写入这个文件
    			fprintf(file1, "%s
    ", line);
    		else if (strstr(line, argv[3]))
    			fprintf(file2, "%s
    ", line);
    		else
    			fprintf(file3, "%s
    ", line);
    
    	}
    
    	fclose(file1);
    	fclose(file2);
    	fclose(file3);
    	return 0;
    }
    
    vs 11 命令行配置选项
    • 像 ps -ef 这类命令的-ef 选项是怎么实现的 ???
    • C语言处理命令行选项的库函数叫getopt(), 每调用一次都会返回命令行中下一个参数.
    比如命令  ./rocket -e 4 -a braisella tokyo londo
    getopt(argc, argv, "ae:")    //其中 ae表示两个选项是有效的, ":" 表示e选项后还需一个参数. 
    
    选项之后的":"冒号表该选项需要接收一个参数
    
    optind 变量保存了getopt()函数从命令行读取了几个选项, 
    下面这两行用来跳过已经读取过的选项.
        argc -= optind;
        argv += optind;  
    
    跳过之后argv的数组将变成
       braisella tokyo londo
    
    示例: 外卖披萨, 指定接收时间和披萨厚薄度
    ./pizza -d now blue gree red -t
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    
    int main(int argc, char *argv[]){
    	char *delivery = "";  //不能定义为字符串,字符串一旦定义内容无法再更改, 只能定义为指针.
    	int thick = 0;
    	int count = 0;
    	char ch ;     //下一个参数
    
    	while((ch = getopt(argc, argv, "td:")) != EOF ) {
    		switch (ch) {
    			case 'd':
    				delivery = optarg;
    				break;
    			case 't':
    				thick = 1;
    				break;
    			default :
    				fprintf(stderr, "unknow option: '%s'
    ", optarg);
    			return -1;
    		}
    	}
    	argc -= optind;
    	argv += optind;
    
    	if(thick)
    		puts("Thick pizza ...
    ");
    
    	if ( delivery[0] )
    		printf("to be delivery %s 
    ", delivery);
    
    	for (count =0; count < argc ; count++){
    		printf("参数 %i 的值是:%s
    ", count, argv[count]);
    	}
    
    	return 0;
    }
    
    vs 12. 为啥把一个很大数保存到小的数据类型里数据溢出的值是负数???
    因为数字以二进制保存, 比如 100 000 的int型二进制位是:
        0001 1000 0110 1010 0000
    而一个 short类型只能保存2个字节即 2^16 = 65536 大小    
    因此  100 000 塞进short里时只能保存2个字节,所以只保存了数字右半边.
    即:
        1000 0110 1010 0000
    而二进制的第一位是有符号数为,值为1表负数, 因此转成十进制后是:
        -31072
    
    
    vs 13. 如果两个整数相除转成浮点数的话,余数会被舍掉,
    • 因此要输出正确浮点数应先把整数保存到float变量里或使用类型显式转换.
    int x = 7;
    int y = 2;
    float z = x / y; //输出的是3.0000, 而不是3.5000
    
    正确的姿势应该是:
        分子/分母同时转换
        float z = (float) x / (float) y;    //输出 3.50000
    
    或分子/分母其中一个转换即可
    	float a = x/(float)y;               //输出 3.50000
    	float b = (float)x/y;               //输出 3.50000
    
    vs 14. 可以在数据类型前加long, 让它变长

    比如 long double d;

    vs15. 看到编译器返回"conflicting type for '某函数名' "的错误.
        这是由于被调用的函数未写在调用语句前,因此编译器不知该函数的返回值是啥, 于是首先假定该函数的返回值为int, 最后找到函数的定义处发现类型不是int的时候就报上面的错误了. [编译器在想: 你怎么跟我之前记录的int返回值不一样呀??? 于是抛出错误]
    
    为避免这种顺序导致的编译错误, 方法有2:
        @1. 在调用前声明一个空方法体. 
            比如: float add_with_tax(float f);  //没有方法体
    完整如下:
        #include <stdio.h>
        float sumTotal(float i);    //声明
        int main(){
            //....
            printf("i=%f
    ",sumTotal(5.5) );
        }
        
        float sumTotal(float i) {
            return i * 2;
        }
        
        @2. 把方法声明体放入一个 *.h 头文件, 然后引入进来
        比如新建一文件func.h 然后里面的代码如下:
            float sumTotal(float i);
        在调用的源文件头部引入上面的func.h, 如下:
        
            #include <stdio.h>          //引入标准库目录下的stdio.h头文件
            #include "/usr/local/func.h"    //引入自定义路径下的头文件
            int main(){
            	printf("i=%f
    ",sumTotal(5.5) );
            }
            
            float sumTotal(float i) {
            	return  i * 2;
            }
        
        PS: 编译器看到尖括号回到标准库代码所在目录里查找文件,但你的头文件不在标准库目录,因此用引号, 在本地查找,否则用尖括号在代码库目录找不到. 
            C代码库目录一般在 /usr/include/ 目录下,如下
            ➜  CLang vim /usr/include/str
                strhash.h     stringlist.h  struct.h
                string.h      strings.h
    
    变量的作用域仅限于本文件中, 如果你想共用变量, 就该在头文件中声明,并在变量名前加上 extern 关键字, 比如
        func.h extern int passcode;
    
    上面的sumTotal()只能自己使用,无法共享, 如果多个程序间要共享某个文件的代码该咋办???
        只需要把sumTotal()代码放到一个独立文件中, 比如common.c
        完整步骤:
        - 新建一个头文件func.h
        - 把sumTotal()方法代码放到common.c 中去
        - 主程序要共享的话必须引入 func.h 头文件后才能使用共享代码里的方法
        - 然后执行编译 gcc message_hider.c  common.c -o message_hider
    
    代码块如下:
    func.h  头文件内容:
        float sumTotal(float i);
    
    common.c 内容:
            float sumTotal(float i) {
            	return  i * 2;
            }
    
    message_hider.c 内容:
    
            #include <stdio.h>          
            #include "func.h"    //引入自定义路径下的头文件
            int main(){
            	printf("i=%f
    ",sumTotal(5.5) );
            }
    
    执行编译  gcc message_hider.c common.c -o message_hider
    
    
    vs 16. 编译的整个过程
    • 预处理, 编译器用 #include预处理指令加载对应的头文件.
    • 编译: 把C语言转成汇编代码
    • 汇编: 生成二进制代码(或目标中间码)
    • 链接: 把全部的目标代码连接在一起,生成一个可执行文件.
    假如我的可执行文件由十几个源文件编译而成, 我只修改了其中一个文件, 全部重新编译的话就是资源浪费了, 那该咋办???
    
    --> 提高编译速度, 不要重新编译所有文件
    编译器编译时会为所有文件生成目标代码, 最后把目标代码连接起来变成可执行文件, 假如能只重新生成被修改后的源文件的目标代码, 那就不用编译所有文件了. 
    
    C源代码  --> 编译器 --> 目标代码 --> 连接器 ---> 可执行文件
    
    比如上面的 gcc launch.c sum.c -o launch
    文件关系如下
    rate.h 有rateRate()和该方法声明
    sum.c  里的subTotal()方法调用了rateRate()方法
    subTotal()的声明在sum.h文件里
    launch.c 里调用了 subTotal()方法来计算结果
    
    因此
        sum.c, rate.h    --> sum.o      |
                                        |--> ./launch 可执行文件
        launch.ch, sum.h --> launch.o   |
    
    实际上是下面两步合成的:
    @1. 把源代码编译成目标文件
    gcc -c *.c     //匹配当前目录下的所有C源文件,-c 告诉编译器你想为所有源文件创建目标文件. 
    
    @2. 然后把所有目标文件链接起来
    gcc *.o -o launch  //陪陪当前目录下的所有目标文件链接问一个launch可执行文件
    
    现在加入你只修改了 sum.c, 只需
    gcc -c sum.c         //不用编译launch.c了
    gcc *.o -o launch 
    
    虽然链接还是重新链接所有,但在编译阶段省去了很多的时间. 
    只要发现源文件, 比目标文件的时间还新, 那么这个源文件就需要重新编译
    但你必须得记住你修改了哪些文件, 尤其当几十几百个文件的时候. 
    
    比如上面的两对依赖关系如下:
        sum.c, rate.h    --> sum.o      
        launch.ch, sum.h --> launch.o   
    
    
    使用make : 一个帮你运行编译命令的工具,make会检查源文件和目标文件的时间戳,如果目标文件过期, 就会重新编译.
     make可以帮你通过时间戳判断文件是否过期, make 还要知道文件间的依赖关系,并且你要告诉它对这种过期的依赖关系使用哪些指令, 才能帮你编译
    
     
     因此make要知道的两个事情:
        @1. 依赖项: 生成目标需要哪些文件. 
        @2. 生成方法: 生成该文件要使用哪些命令
    
    
    这就需要使用makefiles 文件了, 把make索要知道的两个事情都列出来,如下:
    launch.o: launch.c sum.h
    	gcc -c launch.c
    	
    #生成源文件sum.o的依赖关系
    sum.o: sum.c rate.h
    	gcc -c sum.c     #生成源文件的执行命令
    
    #最后去链接目标文件
    launch: launch.o sum.o
    	gcc launch.o sum.o -o launch	#执行的命令(生成可执行文件)
    
    然后在命令窗口下执行
    [root@07 Cproject]# make launch
    gcc -c launch.c
    gcc -c sum.c
    gcc launch.o sum.o -o launch
    
    然后只修改sum.c 再执行 make launch 
    [root@07 Cproject]# make launch
    gcc -c sum.c                     //只编译了sum.c
    gcc launch.o sum.o -o launch	
    
    上面为啥make命令要指明 launch 可执行的文件名??? 
    -> 因为你makefile里可能定义了多个可执行文件的生成, 不止一个launch 
    那难道不能直接像安装php一样就输入了make么?
    ./configure ...
    make 
    make install 
    
    那肯定可以了, 只不过当你只使用make的时候, 你必须在makefile里定义一个all命令, 而且是放置在所有命令前面, 如下:
    all: launch      //默认只输入make的时候执行了make all, 对应的指令组是launch 即系统自己执行 make launch
    
    你再次执行的 make install 也不过是makefile 里定义的一个install命令组, 下面的内容多半是替换/复制编译好的可执行文件到执行目录去. [有些只用一个make就安装成功的, 是因为在make all的其中一个指令中包含了类似的make install动作了]
    
    输入 make launch 的原因是为了执行makefile里对应的launch对应的"gcc launch.o sum.o -o launch" 命令, 而这个又依赖于上面的2个目标文件sum.o, launch.o, 于是一步步推演, 最后到执行成功. 
    
    类似的你也可以用make执行编译
    [root@07 Cproject]# make sum.o
    gcc -c sum.c
    
    再比如在上面的makefile文件后面追加入下面的代码,用于列出当前目录:
    //...
    sayhello:
            pwd
    
    
    [root@07 Cproject]# make sayhello
    pwd                     
    /media/psf/Home/share/Cproject
    
    因此makefile 的作用越看越像是编辑 ~/.bash_profile , 上面的sum.o, sayhello , launch, ... 就像是alias 的命令名, 而下面的就是这个别名命令对应的执行语句
    
    因此makefile 的语法
        all: 多个命令名(放为第一个命令)[可选]
        命令名: [可选依赖关系,空格隔开]
                执行的命令1[可多条]
                执行的命令2
    
    比如曾经麦兜的Makefile代码片段如下:
    install:
            cp ${SRV_BIN_DIR}/router ${SRV_DIR}/router/bin/
            cp ${SRV_BIN_DIR}/game ${SRV_DIR}/game/bin/
            cp ${SRV_BIN_DIR}/broadcast ${SRV_DIR}/broadcast/bin/
    
    bootstrap:
            mkdir -p /data/home/user_00/server
            mkdir -p /data/home/user_00/opsconf/qzone
            cd /data/home/user_00/server && mkdir -p game router broadcast
            cd /data/home/user_00/server/game   && mkdir -p bin conf log
            cd /data/home/user_00/server/router && mkdir -p bin conf log
            cd /data/home/user_00/server/broadcast && mkdir -p bin conf lo
    
    
    ant 是 java 下类似make的勾践工具
    rake 是 ruby 下类似make的构建工具
    生成makefile相当痛苦,要手动编辑, 你可以用autoconf工具来生成 makefile/configure 的文件内容.
    [比如你之前在安装memcached扩展时进入该源码包目录发现没有configure文件, 执行 phpize 报找不到 autoconf , 一般情况下phpize执行完检查后会调用autoconf 生成configure文件, 这里找不到那么你就要yum install autoconf 后再执行phpize, 然后就会生成该扩展的configure 文件了 ]
    
    vs 17. 结构体
        const char * 表示将传递字面值. 
        
        struct fish {
                const char *name;
                const char *species;
                int teeth;
                int age;
        }
        
        实例化:
            struct fish a = {"snappy", "prianha", 69, 4}
     
     @2.结构定义简写:   
        实例化的时候还要把结构类型(struct fish)带上, 那是相当麻烦. 
        可以用typedef 来为结构体命名(起别名)
            typeofdef struct fish {
                //...
            } fishes;
        现在把struct fish 起别名(类型名)fishes了, 所以实例化时可以直接:
            fishes f = {"snappy", "prianha", 69, 4}
        
        或者直接连结构体名都不要,只保留类型名(别名), 如下
        typedef struct {
                //...
        } fishes;
        [唯一例外 :在链表那种递归结构中不能省略结构名]
        
        实例化结构变量过程简化为:
            fishes f = {"snappy", "prianha", 69, 4};
        
        
        把一个结构变量赋值给另一个结构变量, 计算机会赋值结构的内容(副本), 如果结构体中有指针,那么赋值的仅仅是指针的地址值. 
        
        struct fish b = a;
        那么 b 和 a 的name和species 是指向同一块地址的[因为他们是指针,不会复制副本], 而teeth, age是相互独立的一块[地址和存储]. 
        因此函数传参的时候要想改变结构体字段值,形参必须传指针, 否则是拷贝副本的
        
        //不会改变原结构, 因为形参f是源f的一个副本
        happy_fish(f);
        void happy_fish(fish f) {
            f.age = 9;
        }
        
        
        //方法体会改变结构, 因为传的是源f的指针
        happy_fish(&f);
        void happy_fish(fish *f) {
            (*f).age = 9;
            (*f).care.exercise.duration = 9.9;  //多层嵌套的访问
        }
        上面 (*f).age 中括号非常重要, 不加会出错的, 不加表示
        把*放变量名前并括号,表想得到指针指向的值.
        而*t.age 中t表示地址,地址没有age的内容, 直接就报错了. 
        
    仅在指着下可使用"->"表示结构体的访问:
        但这种(*t).age的方式还是太难写了, 可该用"->"
        因此(*f).age 和 t->age 含义相同. 
        因此上面的可改写成:
        void happy_fish(fish *f) {
            t->age = 9;  //由t指向的结构中age的字段
            t->care.exercise.duration = 9.9;  //由t指向的结构中care.exercise.duration的字段
        }
        
        
        ```
     ![image](https://note.youdao.com/yws/public/resource/340d5e702b482d79e98d703e8c6ae291/xmlnote/523696B4EDF34175ACBF9B8B05550861/9269)
        ```
        @3. 结构嵌套
            struct perference {
                const char *food;
                float exercise_hours;
            }
            
            typedef struct {
                const char *name;
                const char *species;
                int teeth;
                int age;
                //嵌套了一个结构体, care表身在fish结构体中的字段名
                struct perference care;
            } fish;
            
            fish f = {"snappy", "water", 64, 4, {"checken", 4.5}};
            printf("teeth=%d,food=%s", f.teeth, f.care.food );
            //输出 teeth=64,food=checken
    
        @4. 结构字段按名访问, 用<jiegou >.<字段> de 点表示法.进行读取和更新
            f.teeth = 90;
            
        
        
    

    结构嵌套加简写:

    #include <stdio.h>
    
    struct exercise {
    	const char *description;
    	float duration;
    };
    
    struct meal {
    	const char *ingredients;
    	float weight;
    };
    
    struct perferences {
    	struct meal food;
    	struct exercise exercise;
    };
    
    typedef struct {
    	const char *name;
    	const char *species;
    	int teeth;
    	int age ;
    	struct perferences care;
    } fish;
    
    int main(){
    	fish f = {"fish", "water life", 64, 4, {{"meal", 2.2}, {"exercise",4.5 } } };
    	printf("teeth=%d, meal=%s, duration=%f", f.teeth, f.care.food.ingredients, f.care.exercise.duration);
    	return 0;
    }
    
    vs 18. union联合
        我的结构体中有三种字段类型, 我可能不会全部都赋值使用, 我不想给三个字段都分配空间,这样太浪费了. 
        联合union结构: 使用时才根据赋值决定你保存的类型(计算机会使用其中最大的字段来分配空间以防止溢出)
        
    typedef union {
        short count;
        float weight;
        float volume;
    } quantity;
    
    初始化联合, 当有两个字段数据类型一样的时候,为了辨别你的目标必须用 ".字段名=val" 的方式来赋值.
    @1. quantity c = {10}
    @2. quantity q = {.weight=1.5, .count=2 }
    
    @3.或者 :
        quantity q;
        q.count = 15;
        
    联合和结构体配合使用
    typedef union {
        short count;
        float weight;
        float volume;
    } quantity;
    
    typedef struct {
        const char *name;
        const char *country;
        quantity amount;
    } fruit_order;
    
    fruit_order apples = {"apples", "england", .amount.weight=4.2};
    printf("weight=%f", apples.amount.weight);  //输出 4.20000
    
    
    看上面结构体重的联合赋值是   .amount.wight=4.2 最前面有个"."点号表这个字段是联合
    
    vs19. 枚举
        enum colors 
        {   
            RED, 
            GREEN,
            PUCE
        };
            使用枚举:
            enum colors favorite = PUCE;  //这个值只能在枚举中的,否则报错
        
        或者简写的方式:
            typedef enum {
                RED,GREEN,PUCE
            } colors;
        
    
            colors cs = PUCE;
    
    vs 20.位字段
        假如你有很多只有几个变化的数据, 比如choice字段的值不是1就是0, 如果我定义一个intshort还是浪费的, 我可不可以之用一个二进制位???
        
        typedef struct {
            unsigned int first_visit:1;
            unsigned int come_again:1;
            unsigned int fingers_lost:4;
            unsigned int shark_attack:1;
            unsigned int days_a_week:3;
            //...
        } synth;
    
    每个字段必须是 unsigned int, 后面的:1表示使用1个位空间,即只有0,1, 而:3 表示0-7的值共8种
    synth sy = {1,0,15,0,7};
    
    

  • 相关阅读:
    详解用em替换px
    js判断是否为ie浏览器
    nth-child()选择器小结
    HTML5之canvas
    MQTT-SN协议乱翻之消息格式
    MQTT-SN协议乱翻之简要介绍
    MQTT 3.1.1,值得升级的6个新特性
    MQTT 3.1协议非严肃反思录
    MQTT协议笔记之mqtt.io项目HTTP协议支持
    MQTT协议笔记之mqtt.io项目Websocket协议支持
  • 原文地址:https://www.cnblogs.com/zyp221314/p/9186624.html
Copyright © 2020-2023  润新知