嗨翻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\n", p);
	printf("p+1=%p \n", (p+1) );   		 //打印第二个元素的地址
	printf("*(p+1)=%c \n", *(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\n", p);			//打印第一个元素的地址
	printf("p+1=%p \n", (p+1) ); 		//打印第二个元素的地址
	printf("*(p+1)=%d \n", *(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\n",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\n", b ); //输出 "hello world";      当字符串
printf("%p \n", b); //输出 0x7fff5a63878   当地址


vs 5. 特殊的变量字符串 - 在C语言中字符串其实是char数组
char *cards = "jqk";				//"jqk"是存放在常量区的,只读
printf("cards=%p\n", cards);			//cards 获得了常量字符串的第一个元素的指针
printf("cards=%p\n", cards+1);		//输出第二个元素地址
printf("cards=%p\n", cards+2);		//输出第3个元素地址
printf("*cards=%c\n", *cards);		//输出第1个字符元素
printf("*cards=%c\n", *(cards+1));	//输出第2个字符元素
printf("*cards=%c\n", *(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 \n", 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\n",  *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[^\n]", &latitude, &longitude, info) 函数等待用户输入,  
其实屏幕输入和  "< gpsdata.csv" 导入文件是一样的, 都是标准输入stdin, 因此可用后者取代前者. 

前半部分获取标准输入到程序中去, 然后程序的标准输出到output.json 文件去
一般情况下使用 
printf() 默认会把文本发送到标准输出stdout 去, 假如你要实现正常的发送到标准输出, 异常的发送到标准错误stderr去, 该咋办???
"<" 标准输入 stdin
 ">" 的作用是重定向标准输出 stdout
 "2 > " 是重定向标准错误.  stderr 


假如你原来的
printf( "invalid latitude. %f\n", latitude);  该类错误信息不用弄到标准输出去, 否则会被捕获到  > output.json去的, 你该咋办???
使用 fprintf(stderr, "invalid latitude. %f\n", 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!\n");
		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[^\n]\n", line) ==1 ) {
		if (strstr(line, argv[1]) ) //搜索到这个字符就写入这个文件
			fprintf(file1, "%s\n", line);
		else if (strstr(line, argv[3]))
			fprintf(file2, "%s\n", line);
		else
			fprintf(file3, "%s\n", 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'\n", optarg);
			return -1;
		}
	}
	argc -= optind;
	argv += optind;

	if(thick)
		puts("Thick pizza ...\n");

	if ( delivery[0] )
		printf("to be delivery %s \n", delivery);

	for (count =0; count < argc ; count++){
		printf("参数 %i 的值是:%s\n", 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\n",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\n",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\n",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};

posted @ 2018-06-15 11:41  大鹏鸟2018  阅读(771)  评论(0编辑  收藏  举报