【12-1 结构体 共用体 枚举】
typedef深入分析
定义
用自定义名字为已有数据类型命名。其实叫 typerename 更合适。 形如:typedef 现在类型名 新类型名;
typedef 和#define 的区别
typedef 是以;号结尾的 C 语言语句。而#define 则是预处理阶段的文本替换。有时
他们是可以互换的,但有时不可以。
typedef a b; #define a b
Tips
1.typedef char *pChar; pChar a; a等同于?
试着预测输出并调试以下代码:
{ char *p,q; printf("sizeof(p) = %d sizeof(q) = %d \n",sizeof(p),sizeof(q));// 4,1 typedef char *pChar; pChar a,b; printf("sizeof(a) = %d sizeof(b) = %d \n",sizeof(a),sizeof(b));// 4, 4 #define DpChar char*; DpChar m,n; printf("sizeof(m) = %d sizeof(n) = %d \n",sizeof(m),sizeof(n));// 4 ,1 }
2.总结
新类型名一般用大写表示,以便于区别。
用 typedef 只能声明新的类型名,不能创造新的类型,只是为已经存在的类型起
一个别名,也不能用来定义变量,即只能用其声明的类型来定义变量;
有时也可用宏定义来代替 typedef 的功能,但是宏定义是由预处理完成的,而
typedef 则是在编译时完成的,更为灵活方便。
typedef 可以让类型更见名知意,更便于移值。
结构体的初始化
初始化及成员访问
点成员运算符(.) 优先级等同-> 但比*高
Tips
问题一 初始化和赋值在c++中实际含义是? 问题二 形参和实参之间传递是什么关系? 问题三 typedef和#define 本质区别在哪里? 问题四 结构体作形参为什么要传指针?
结构体数组及应用
结构体嵌套和结构体大小
结构体嵌套
结构体中,嵌套结构体,称为结构体嵌套。结构体中,既可以嵌套结构体类型变量, 也可以嵌套结构体类型,后一种方式不推荐。
结构体类型大小
结构体成员内存分布
首成员在低地址,尾成员在高地址。
内存对齐
对齐规则
目的是解决:一个成员变量需要多个机器周期去读的现象,称为内存不对齐。为什么要对齐
呢?本质是牺牲空间,换取时间的方法。
不同的编译器和处理器,其结构体内部的成员有不同的对齐方式
x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。
方法:
①取 pack(n)的值(n= 1 2 4 8--),取结构体中类型最大值 m。两者取小即为外对齐大 小 Y= (m<n?m:n)。 ②将每一个结构 体的成员大小与 Y 比较取小者为 X,作为内对齐大小. ③所谓按 X 对齐,即为地址(设起始地址为 0)能被 X 整除的地方开始存放数据。 ④外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。
外对齐和内对齐:
外对齐Y:保证读取结构体的起始地址到结束地址,表示结构体之间的对齐 内对齐X:保证从结构体内变量起始的地址到结束的地址 正好是该变量的长度,结构体内成员变量之间的对齐
结构体中指针使用注意事项
1.向结构体内未初始化的指针拷贝
结构体中,包含指针,注意指针的赋值,切不可向未知区域拷贝。
struct student { char*name; int score; }stu; int main() { strcpy(stu.name,"Jimy"); stu.score=99; return 0; }
name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在
调用 strcpy 函数时,会将字符串 “Jimy” 往乱码所指的内存上拷贝,内存 name 指针根
本就无权访问,导致出错。 同样stu.name = “Jimy”;可以的,name指向常量区,但是将来name不可改
int main() { struct student *pstu; pstu = (struct student*)malloc(sizeof(struct student)); strcpy(pstu->name,"Jimy"); pstu->score=99; free(pstu); return 0; }
为指针变量 pstu 分配了内存,但是同样没有给 name 指针分配内存。错误与上面
第一种情况一样,解决的办法也一样。这里用了一个 malloc 给人一种错觉,以为也给
name 指针分配了内存。
2.未释放结构体内指针所指向的空间
从内向外依次释放空间。
Tip
1.结构体中嵌套构造类型成员的对齐(数组、结构体成员)
2.深拷贝和浅拷贝
深拷贝:拷贝内存的内容,结构体之间互不影响。 浅拷贝:直接地址赋值,指针共享一片内存。一个结构体发生变化,另一个结构体也会发生变化。
【13-2 单链表】
【14-2 文本文件和二进制文件】
文件流
C 语言把文件看作是一个字符的序列,即文件是由一个一个字符组成的字符流,因 此 c 语言将文件也称之为文件流。即,当读写一个文件时,可以不必关心文件的格式或
结构。
文件类型
文件,物理上是二进制,所以文本文件与二进制文件的区别并不是物理上的,而是逻辑上的。
文本文件是基于字符编码的文件,常见的编码有 ASCII 编码,二进制文件是基于值编码
的文件。
文本文件
以 ASCII 码格式存放,一个字节存放一个字符。 文本文件的每一个
字节存放一个 ASCII 码,代表一个字符。这便于对字符的逐个处理,但占用存储空间
较多,而且要花费时间转换。
二进制文件
以值(补码)编码格式存放。二进制文件是把数据以二进制数的格
式存放在文件中的,其占用存储空间较少。数据按其内存中的存储形式原样存放。
用例:
int main() { short a = 10000; FILE * fp = fopen("ascii.txt", "w"); fprintf(fp, "%d", a);//文本写 fclose(fp); FILE *fp2 = fopen("bin.txt", "w"); char buf[] = "abcd"; fwrite(&a, 2, 1, fp2);//字节写 //fwrite(buf, 4, 1, fp2);//字节写 fclose(fp2); return 0; }
文件缓存
为什么要有缓冲区(buffer) 原因为多种,有两个重点:
1 从内存中读取数据比从文件中读取数据要快得多。
2 对文件的读写需要用到 open、read、write 等系统底层函数,而用户进程每调用
一次系统函数都要从用户态切换到内核态,等执行完毕后再返回用户态,这种切
换要花费一定时间成本(对于高并发程序而言,这种状态的切换会影响到程序性
能)。
文件的打开和关闭
FILE 结构体
FILE 结构体是对缓冲区和文件读写状态的记录者,所有对文件的操作,都是通过 FILE 结构体完成的。
typedef struct { short level; /* 缓冲区满/空程度 */ unsigned flags; /* 文件状态标志 */ char fd; /* 文件描述符 */ unsigned char hold; /* 若无缓冲区不读取字符 */ short bsize; /* 缓冲区大小 */ unsigned char *buffer; /* 数据传送缓冲区位置 */ unsigned char *curp; /* 当前读写位置 */ unsigned istemp; /* 临时文件指示 */ short token; /* 用作无效检测 */ } FILE ; /* 结构体类型名 FILE */
在开始执行程序的时候,将自动打开 3 个文件和相关的流:标准输入流(stdin)、标
准输出流(stdout)和标准错误(stderr),它们都是 FIEL*型的指针。流提供了文件和程序的
通信通道。
fopen
如果读写的是二进制文件,则还要加 b,比如 rb, r+b 等。 unix/linux 不区分文本和
二进制文件。
fclose
作用:强制输出缓存内容 然后关闭FILE*
文件的读和写
一次读一个字符
fputc
fgetc
feof
特点:feof 这个函数,是去读标志位判断文件是否结束的。即在读到文件结尾的时候再
去读一次,标志位才会置位,此时再来作判断文件处理结束状态,文件到结尾。如果用
于打印,则会出现多打一次的的现象
一次读一行字符
windows 换行符 ‘\n’ = 0x0d 0a;
linux 换行符 ‘\n’ = 0x 0a;
tips
1.fprintf(fp,fmt,buff) 文本写出
fwrite(buff,size,count,fp) 字节写出
2.乱码原由
二进制文件读取由acsii码的方式读取
3.文件缓存win和linux区别
win会立即输出,linux会等待缓存满了再输出,加上\n会立刻输出缓存
4.rewind(fp) 将文件指针重置到文件头
位运算符
预处理
预处理操作,不是 c 语言语句,故语句末尾没有分号,在预处理阶段完成,本质是替
换操作。
发生时段:
宏定义变量
不带参宏:
#define 定义的宏,只能在一行内表达(换行符表示结束而非空格),如果想多行表
达,则需要加续行符。
#define PI 3.14\ 15926
宏常量,常被 const/ enum 变量取代,用于定义文件路径则被常用。
#define FILEPATH "E:\\English\\listen_to_this\\listen_to_this_3" #define ERR_EXIT(m)\ do\ {\ printf("Err:%s",m);\ exit(-1);\ }while(0) //此处的分号,可有可无
宏常量的缺陷 解决这一些问题,要不吝惜使用括号。
#define N 2+3 // #define N (2+3) int main(void) { int num = N*2; return 0; }
宏类型:
宏可以给类型起别名,因其缺点,常被 typedef 取代
#define CHARP char * int main(void) { CHARP p,q; printf("p = %d q = %d\n",sizeof(p),sizeof(q)); return 0; }
带参宏(宏函数)
#define str(x) x
#define str(x) “aa”#x"bb" //#字符串化
#define str(x) x*2 \
x+x // ‘’ 续行符号
#undef MAX
条件编译
#ifdef #ifndef
#elif
#endif
预定义宏
头文件包含的意义
全写入,被包含的文件中。包含是支持嵌套的。
方式<>
#include<stdio.h>,从系统指定路径中搜索包含头文件,linux 中的系统路径为
(/usr/include)
方式" "
#include"myString.h",从工程当前路径中搜索包含头文件,如果当前工程路径下
没有的话,则到系统路径下搜索包含。
其他
#运算符 利用宏创建字符串
将替换符 字符串化,解决字符串中,不可被替换的参数问题。字符串如下的书写
也是合理的。
//#define str(x) #x //#define str(x) "aaaaaaaaxaaaaaaaaa" #define str(x) "aaaaaaaa"#x"aaaaaaaaa" int main() { printf("%s\n",str(100)); return 0;
##运算符 预处理的粘和剂
#解决了双引号中无法替换问题,##解决了非双引号中粘连无法替换的问题。
//#define sum(a,b) (aa+bb) #define sum(a,b) (a##a+b##b) int main() { printf("%d\n",sum(2,3)); return 0; }
预定义宏
DATE 进行预处理的日期(“MMmm dd yyyy"形式的字符串文字)
FILE 代表当前源代码文件名的字符串文字
LINE 代表当前源代码中的行号的整数常量
TIME 源文件编译时间,格式"hh:mm:ss”
func 当前所在函数名
在打印调试信息时打印这两
个宏 FILELINE 可以给开发者非常有用的提示
变参函数
参数的个数也是可变的,也就是说,在形参表中可以不明确指定传递参数的个数和类型,一个常见的库函数 printf()就是如此。这种函数称之为变参函数。
#include <stdio.h> #include <stdlib.h> #include <stdarg.h> //num 个 int 型数相乘 int mul(int num, int data1, ... ) { int total = data1; int arg, i; va_list ap; va_start(ap, data1); for(i = 1; i < num; i++) { arg = va_arg(ap, int); total *= arg; } va_end(ap); return total; } //i 个 int 型数相乘 long mul2(int i, ...) { int *p, j; p = &i + 1; //p 指向参数列表下一个位置 long s = *p; for (j = 1; j < i; j++) s *= p[j]; return s; } int main() { printf("%d\n", mul(3, 2, 3, 5)); printf("%d\n", mul2(3, 2, 3, 5)); return 0; }
变参宏
VA_ARGS 是一个可变参数的宏,这个可变参数的宏是新的 C99 规范中新增
的,目前似乎只有 gcc 支持(VC6.0 的编译器不支持)。宏前面加上##的作用在于,当
可变参数的个数为 0 时,这里的##起到把前面多余的","去掉的作用,否则会编译出错, 你
可以试试。
#define debug(...) printf(__VA_ARGS__) #define dgbmsg(fmt,...) printf(fmt,__VA_ARGS__) #define LOGSTRINGS(fm, ...) printf(fm,__VA_ARGS__) #define debug(format, ...) fprintf (stderr, format, __VA_ARGS__) #define debug(format, args...) fprintf (stderr, format, args)
在标准 C 里,你不能省略可变参数,但是你却可以给它传递一个空的参数。例如,
下面的宏调用在 ISO C 里是非法的,因为字符串后面没有逗号:
debug (“A message”)
GNU CPP 在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译器仍
然会有问题(complain),因为宏展开后,里面的字符串后面会有个多余的逗号。
CPP 使用一个特殊的’##’操作。书写格式为:
#define debug(format, …) fprintf (stderr, format, ## VA_ARGS)
这里,如果可变参数被忽略或为空,’##’操作将使预处理器(preprocessor)去除掉
它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU CPP 也会工作
正常,它会把这些可变参数放到逗号的后面。象其它的 pasted macro 参数一样,这些参
数不是宏的扩展。