目录
一、qsort函数
1.库函数qsort函数
2.自己实现的qsort函数
二、大小端字节序存储模式
三、截断与整型提升
四、字符串处理函数
1.strlen函数
2.strcpy函数
3.strcat函数
4.strcmp函数
5.strncopy函数
6.strncat函数
7.strncmp函数
8.strstr函数
五、内存处理函数
1.memcpy函数
2.memmove函数
3.memcmp函数
4.memset函数
六、自定义数据类型
1.结构体
2.共用体(联合体)
3.枚举常量
七、函数的值传递和址传递
1.值传递:
2.址传递
*八、返回栈空间地址导致代码错误
九、缓冲文件系统的输入/输出函数
1.gets/puts函数
2.fgets/fputs函数
3.fgetc/fputc函数
4.scanf/printf函数
5.fscanf/fprintf函数
6.sscanf/sprintf函数
7.fopen/fread/fwite/fclose函数
8.fseek/ftell/rewind函数
9.strerror/perror函数
10.ferror/feof函数
十、C语言预处理
1.源文件 --> 可执行文件
2.系统的预定义符号
3.#define定义的符号和宏
4.#define里边的#和##
一、qsort函数
1.库函数qsort函数
((void*)空指针类型:
可以接受所有类型的数据的地址,不能直接接引用和加减操作访问地址对应的值,在解引用前应进行强制类型转换(例如e1为void*类型指针,原*e1对应的数据类型为整型,正确的解引用操作为*(*int)e1)
①函数功能:实现对任意类型数据的排序
②引用头文件#include
③函数原型:void qsort(void *start,int length,int width,int(*cmp)(const void*e1,const void* e2));
start:待排序数组的首元素的地址
length:数组的元素个数
width:数组的元素大小
int(*cmp)(const void* e1,const void* e2):比较函数指针
④比较函数写法:
因为涉及到传参过程,需要保证函数的参数以及返回值类型保持一致:
int _cmp(const void* e1,const void* e2);e1>e2返回值大于0(升序排列)
e1=e2返回值等于0
e1
当函数返回值大于0时便会进行两个元素之间的交换
⑤对不同类型数据排序的写法:
a.整型:
qsort函数: qsort(arr,sz,sizeof(arr[0]),cmp_int); 比较函数写法: int cmp_int(const void* e1,const void* e2) { return *(int*)e1-*(int*)e2;//升序排列 或者:return *(int*)e2-*(int*)e1;//降序排列 }b.浮点型:
qsort函数: qsort(f,sz,sizeof(f[0]),cmp_float); 比较函数: com_float(const void* e1,const void* e2) { if(*(float*)e1==*(float*)e2) return 0; else if(*(float*)e1>*(float*)e2)//升序排列 降序排列:(*(float*)e1<*(float*)e2) return 1; else return -1; }c.结构体:
struct Stu { int age; char name[10]; }; int cmp_struct_by_age(const void* e1, const void* e2)//比较函数定义方法 { (升序排列)return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//注意访问成员变量的方法 (降序排列)return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age; } int main() { struct Stu stu[3] = { {18,"孙悟空"},{19,"猪八戒"},{20,"唐僧"} }; int sz = sizeof(stu) / sizeof(stu[0]); qsort(stu, sz, sizeof(stu[0]), cmp_struct_by_age); for (int i = 0; i < sz; i++) { printf("%d %sn", (stu + i)->age, stu[i].name); }2.自己实现的qsort函数
#includevoid swap(char* e1, char* e2, int width) { for (int i = 0; i < width; i++) { char tmp = *e1; *e1 = *e2; *e2 = tmp; e1++; e2++; } } void bubble_sort(void* base, int sz, int width, int (*cmp)(void* e1, void* e2))//注意函数指针参数 { for (int i = 0; i < sz-1; i++) { for (int j = 0; j < sz-1-i; j++) { if ((*cmp)((char*)base+j*width,(char*)base+(j+1)*width)>0)//强转为char*类型,加上宽度来达到控制任意数据类型地址的目的 { swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_int(char* e1, char* e2)//整型交换函数 { if (*(int*)e1 < *(int*)e2)//降序排列 { return 1; } else if (*(int*)e1 == *(int*)e2) { return 0; } else { return -1; } } int cmp_float(void* e1, void* e2)//浮点型交换函数 { if (*(float*)e1 > *(float*)e2)//升序排列 { return 1; } else if (*(float*)e1 == *(float*)e2) { return 0; } else { return -1; } } void Print_int(int arr[], int sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } } void Print_float(float f[], int sz) { for (int i = 0; i < sz; i++) { printf("%.1f ", f[i]); } } int main() { int arr[] = { 1,3,5,7,9,2,4,6,8,10,0 }; int sz_arr = sizeof(arr) / sizeof(arr[0]); float f[] = { 2.0,3.6,5.8,9.4,5.5,2.8,7.1 }; int sz_f = sizeof(f) / sizeof(f[0]); bubble_sort(arr, sz_arr, sizeof(arr[0]), cmp_int); Print_int(arr, sz_arr); bubble_sort(f, sz_f, sizeof(f[0]), cmp_float); Print_float(f, sz_f); return 0; }
二、大小端字节序存储模式
1.图示:
大端存储:低字节存放在高地址处,高字节存放在低地址处。
小端存储:低字节存放在低地址处,高字节存放在高地址处。
2.判断电脑大小端
通过共用体判断 #includeunion Judge { short a;//2字节 char arr[2];//1字节*2 }; int main() { union Judge judge; judge.a = 0x1234; printf("%xn", judge.arr[0]);//虽然没有对arr赋值,但作为共用体,它的内存空间已经被占用了 printf("%xn", judge.arr[1]); return 0; } 打印结果:34,12. 地字节存放在了低地址,说明当前机器存储类型为小端存储
三、截断与整型提升
1.char类型数能够表示的范围:
char类型数据占一个字节,8个比特位,最高位为符号位,能表示的数据范围为-128-127(2^7-1),规定10000000为-128. unsigned char类型数据占一个字节,8个比特位,能表示的数据范围为0-255(2^8-1).图示:
2.整型提升:
对于有符号数,整型提升时高位补符号位。
对于无符号数,整型提升时高位补零。
例如:
unsigned char a=200; unsigned char b=100; unsigned char c=a+b; printf("%d,%d",a+b,c); --------------------- 运行结果应为300,44. char类型a的二进制表示形式为11001000,char类型b的二进制表示形式为01100100; 二者在进行加减操作之前先进行整型提升,由于无符号数整型提升时最高位补零,因此整型提升后的结果为: a:00000000000000000000000011001000; b:00000000000000000000000001100100; a+b:00000000000000000000000100101100; 无符号数原反补码相同,又c中最多能存放8个比特位,因此发生截断,截断后c内存存放00101100,对应十进制数字44.
四、字符串处理函数
一,长度不受限制的字符串处理函数
1.strlen函数
1.函数原型:size_t strlen(const char*str); size_t为typedef重命名的unsigned int
2.返回值:无符号整数
3.函数功能:求字符串长度
4.实现原理:遇‘ ’计数器停止计数,返回‘ ’前的字符数
例如:
int n=strlen("abcdef"); 则a=6;2.strcpy函数
1.函数原型:char* strcpy(char* dst,const char* src);
2.函数功能:将src字符串内容复制到dst字符串中,使得dst和src的内容一致.
3.实现原理:将src字符串中的‘ ’也复制了过去
4.返回值:返回dst字符串的首地址
例如:
char dst[]="abcdef"; char src="efg"; strcpy(dst,src); det字符串的内容变为:"efg"3.strcat函数
1.将字符串src连接到字符串dst后,包含‘ ’.
2.函数原型:char* strcat(char *dst,const char* src);
3.实现原理:从dst字符串末尾的‘ ’(包含‘ ’)开始,将src字符串连接,包含src末尾的' '.
4.函数返回值:返回连接后的字符串的首地址.
例如:
char str[30] = "abcde"; char src[] = "fgghj"; char *p = strcat(str, src); printf("%s", str);4.strcmp函数
1.函数原型:int strcmp(const char* str1,const char* str2);
2.函数功能:比较两个字符串的大小。
3.实现原理:比较两个字符串的同一个位置字符,相等继续比较下一个位置的字符,没有遇到' '前,若字符串1同一位置字符的ascii码值大于第二个字符串相同位置的字符的ascii码值,则字符串1大于字符串2,返回一个大于0的数字;反之,返回一个小于0的数字。若2者都比较到‘ ’的位置,则字符串1等于字符串2,返回数字0.
4.例子:
char* str1 = "abcdef"; char* str2 = "abcdee"; int ret = strcmp(str1, str2); ret应为大于零的数二,长度受限制的字符串处理函数
5.strncopy函数
1.函数原型:int strncpy(char *dst,const char* src,size_t num);
size_t:==unsigned int; num待拷贝的字节数
2.函数功能:将src字符串里的num个字符拷贝到字符串dst中。
3.注意事项:拷贝num个字符后,不再dst末尾追加‘ ’;当拷贝数大于src实际字符数时,在dst的末尾追加‘ ’直至拷贝num个字符。
例子:
char str1[] = "abcdef"; char str2[] = "def"; strncpy(str1, str2, 3); 输出str1:defdef *如果待拷贝字符数大于src实际的字符数,则在dst末尾追加' '直至拷贝num个字符: char str1[10] = "abcdefgh"; char str2[] = "def"; strncpy(str1, str2, 6); 则拷贝后dst的内容为def; 如下图:6.strncat函数
1.函数原型:char* strncat(char* dst, ocnst char*src, size_t num);
2.函数功能:将src中的n个字符追加到字符串dst的末尾,并在追加结束后在dst的末尾放上' '际的;当录入的字符个数大于src字符串的长度时,正常进行追加后再dst末尾追加‘ ’后,多余的要追加的字符个数不做处理。
3.例子:
char dst[30] = "abcdefg"; char src[] = "hijklmn"; strncat(dst, src, 3); 输出结果dst:abcdefghij7.strncmp函数
1.函数原型:int strncmp(const char* str1,const char* str2,size_t count);
2.函数功能:将str1与str2中的n个字符进行比较,原理与strcmp相似。
3.例子:
char str1[] = "abcd"; char str2[] = "abcdefg"; int ret = strncmp(str1, str2, 5); 结果:ret<08.strstr函数
1.函数原型char* strstr(const char*str1,const char*str2);
2.函数功能:找出str2字符串在str1字符串中首次出现的位置,(不包括str2的串结束符)。
如果找到了,返回子串第一次出现的位置的指针,如果没有找到,返回NULL。
3.例如:
char str1[]="abcdef"; char str2[]="cdef"; char* ret=strstr(str1,str2); if(ret==NULL) { return -1;//没有找到 } else { return 1; }
五、内存处理函数
*头文件:string.h
1.memcpy函数
1.函数原型:voi* memcpy(vo
id* dst,const void*src,size_t num);
num:拷贝的字节数,单位字节。
2.函数功能:处理没有交叉的数据的拷贝实现任意类型数据间的拷贝,也可以处理交叉数据。
3.实现原理:
while (num) { *(char*)dst = *(char*)src; ((char*)dst)++, ((char*)src)++; num--; }4.例子:
int dst[10] = { 0 }; int src[] = { 1,2,3,4,5 }; memcpy(dst, src, sizeof(src)); 运行结束后dst:1,2,3,4,5 如果: int arr[]={1,2,3,4,5,6,7,8,9}; memcpy(arr+2,arr,4*5); memcpy无法处理此类交叉数据的拷贝,需要用到内存处理函数memmove()2.memmove函数
1.函数原型:void* memmove(void* dst,const char* src,size_t num);
2.函数功能,实现原理:与函数memcpy类似,可用于处理交叉数据的拷贝
3.例子:
处理重叠: int arr[] = { 1,2,3,4,5,6,7,8,9 }; memmovev(arr + 2, arr, 4 * 5); 结果arr:1,2,1,2,3,4,5,8,9 处理不重叠: 和memcpy相同4.模拟实现:
//自己的写法: #include#include #include void* My_memmove(void* dst, const void* src, size_t num) { assert(dst && src);//1,2,3,4,5,6,7,8,9,10 void* p = dst; const void* pl = src; if ((char*)src + num < (char*)dst)//没有交叉 { while (num--) { *(char*)dst = *(char*)src; ++(char*)dst, ++(char*)src; } return p; } else//有交叉 { void* pr = malloc(num); void* ptr = pr;//指向动态内存pr的指针 //如何的到一摸一样的数组 int num_cpy = (int)num; while (num_cpy--) { *(char*)ptr = *(char*)src; ++(char*)ptr, ++(char*)src; } ptr = pr; while (num--) { *(char*)dst = *(char*)ptr; ++(char*)dst, ++(char*)ptr;; } return p; } } int main() { int dst[10] = { 5,5,5,5,5 }; int src[] = { 6,6,6, }; My_memmove(dst, src, sizeof(src)); for (int i = 0; i < 10; i++) { printf("%d ", *(dst + i)); } return 0; } 3.memcmp函数
1.函数原型:int memcmp(const void* arr1,const void* arr2,size_t num);
2.函数功能:比较两个数据类型的num个字节是否相同,相同返回0,arr1>arr2返回大于0的数字,arr1
例如:
int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 1,2,3,3,3 }; int ret = memcmp(arr1, arr2, 4 * 4); 结果:ret=1>04.memset函数
1.函数原型:void* memset(void* arr,int set_char,size_t num);
其中:set_char:设置的内容;num:字节数,单位字节。
2.函数功能:为内存赋值。
3.例子:
char str[10] = { 0 }; memset(str, '#', sizeof(str)); 结果str:##########
六、自定义数据类型
*结构体,枚举,联合体
1.结构体
1.定义与初始化:c语言大学使用教程。
2.使用的注意事项:
①、 typedef struct S { int a; char name[20]; }S; *使用了typedef关键字将struct S重命名为S,之后创建变量: struct S s1;等价于S s1; ②、同一结构体的成员变量不能包含自身,例如: strcut node { int a; struct node s1; //无法为该结构体类型申请确定大小的空间 };是错误的 要写成: strcut node { int a; struct node* next; };3.内存对齐
①、结构体对齐规则:
*第一个成员在与结构体变量偏移量为0的地址处;
*其他成员要对齐到某个数字(对齐数)的整数倍处;
*对齐数:编译器默认的对齐数与该成员变量所占内存的较小值,VS默认值为8;
*结构体总大小为成员变量里边的最大对齐数的整数倍;如果嵌套了结构体,嵌套的结构体对
齐到自己最大的对齐数的整数倍处,结构体的整体大小就是所有对齐数的最大值(包含嵌套的结构体)的整数倍;
②、结构体默认对齐数可以用预处理指令进行修改:
#pragma pack(2)//设置默认对齐数为2 struct S { char a; int b; int c; }; #pragma pack()//取消设置的对齐数,还原默认数值 *未设置默认对齐数时的结构体大小为12; *设置对齐数后的结构体大小为10;4.位段
①、定义方式类似于结构体:
struct S { int _a : 5; int _b : 10; int _c : 14; int _d : 22; }; 上述位段所占的存储空间为8个字节,冒号后的数字为数据所占比特位数; 在为该位段分配储存空间时,先查阅到int类型,开辟4个字节32个比特位的空间,可以存下a,b,c共29个比特位的数据,再存d时剩余的3个比特位不足以存放,再开辟新的存储空间即4个字节32个比特位存放d,因此上述位段共占8个字节的空间2.共用体(联合体)
1.声明样式:
union S { int a; char str[2]; };2.共用体内成员共同占用同一块存储空间,所占存储空间的大小为成员中所占空间大小的最大值,如上述共用体所占内存空间的大小为4字节。
3.枚举常量
1.声明样式:
enum Myenum { exp1, exp2, exp3…… };2.枚举常量可以指定值也可以不指定。不指定值时枚举常量的值时从第一个成员开始依次为0,1,2……,指定成员变量的值时,以下的成员依次递增1。
①enum MyEnum { ONE,//值为0 TWO=5, THREE//值为6 }; ②enum MyEnum { ONE,//值为0 TWO,//值为1 THREE.。值为2 }; ③enum MyEnum { ONE=5, TWO,//值为6 THREE//值为7 };
七、函数的值传递和址传递
1.值传递:
传递常量或者变量的数值
:例如:
值传递①: void Modify(int a) { a = 10; } int main() { int a = 5; Modify(a); printf("%dn", a); return 0; } 值传递②: void Modify(int* p) { *p = 10; } int main() { int a = 5; int* p = &a; Modify(p);//传参是一个指针变量,所以用指针变量接收 printf("%dn", a); return 0; }//传递了指针变量p的值,在函数内部开辟了一个新的指针变量p`存放了与指针p相同的指向整型a的地址,并非址传递2.址传递
传递常量或者变量的地址
例如:
址传递①: void Modify(int* p) { *p = 10; } int main() { int a = 5; Modify(&a);//实参传递了整型常量a的地址,所以形参用指针变量接收 printf("%dn", a); return 0; } 址传递②: void Modify(int** p)//用二级指针接收(存放一级指针变量的地址) { *(*p) = 10; } int main() { int a = 5; int* p = &a; Modify(&p);//传递了指针变量的地址 printf("%dn", a); return 0; }3.面试题:
判断运行结果,找出代码中的错误: #include#include #include void Getmemory(char* p)//形参用指针变量接收 { p = (char*)malloc(100);//p在函数内部申请内存空间后未释放也无法在函数外部释放,造成了内存泄漏 if (p == NULL) { exit(0); } } int main() { char* str = NULL; Getmemory(str);//错误①,使用了值传递,传递了指针变量的值,在函数中不能为str分配内存空间 strcpy(str, "Hellow,world"); printf(str); free(str); str = NULL; return 0; } *正确解法:
正确解法①(址传递): #include#include #include void Getmemory(char** pr)//形参设计为二级指针(接收一级指针变量的地址) { *pr = (char*)malloc(100);/);//w:写的方式打开,文件存在则覆盖,文件不存在则新建 if (pf == NULL) { printf("%sn", strerror(errno)); return 0; } //成功打开 //.... fclose(pf); pf=NULL; 2.fread: 函数原型:size_t fread(void* buffer,size_t Element_size,size_t Element_count,FILE* pf) 函数功能:从pf指向的文件中读取size*count个字节存到buffer指向的内存区。 返回值:返回Element_count(数据项个数)。若遇文件结束或出错,返回0. 例如:#include #include #include int main() { char str[30] = { 0 }; FILE* pf = fopen("baixiaoshuai.txt", "r"); if (pf == NULL) { printf("%sn", strerror(errno)); return 0; } //成功打开 fread(str, sizeof(char), 5, pf);//不会在字符串末尾添加' ' printf(str); fclose(pf); pf=NULL; return 0; } 3.fwrite: 函数原型:size_t fwrite(const void* buffer,size_t Element_size,size_t Element_count,File* pf) 函数功能:从str所指向空间的size*count个字节输出到fp所指向的文件中。 返回值:写到fp文件中的数据项的个数(count) 例如: int main() { char str[] ="baixiaoshuai"; FILE* pf = fopen("baixiaoshuai.txt", "w"); if (pf == NULL) { printf("%sn", strerror(errno)); return 0; } //成功打开 fwrite(str, sizeof(char), strlen(str), pf);//从str指向的内存块中读取1*16个字节输出到pf对应的文件baixiaoshuai.txt中。 fclose(pf); pf = NULL; return 0; } 4.fclose: 函数原型:int fclose(FILE* pf) 函数功能:关闭fp指向的文件。 返回值:成功返回0,否则返回非0。 例如:FILE* pf = fopen("baixiaoshuai.txt", "w"); if (pf == NULL) { printf("%sn", strerror(errno)); return 0; } //成功打开 fclose(pf);//关闭文件 8.fseek/ftell/rewind函数
1.fseek: 函数原型:intt fseek(FILE* pf,long offset,int origin) 函数功能:移动文件指针到特定的位置。 返回值:成功返回,否则返回非0.在无法搜索的设备上,返回值未定义 ogigin取值及其含义:SEEK_CUR---从文件指针当前位置开始偏移 SEEK_END---从文件指针末尾开始偏移 SEEK_SET---从文件指针起始位置偏移 例如: #includeint main() { FILE* pf = fopen("text.txt", "r");//文件中存的是agcdef if (pf == NULL) { perror("text.txt error");//输出错误信息样式:text.txt error:No Such File Or Directory. return 0; } //成功打开 fseek(pf, -2, SEEK_END); printf("%cn", fgetc(pf));//e fclose(pf); pf = NULL; } 2.ftell: 函数原型:long ftell(FILE* pf) 函数功能:获得当前文件指针的位置 返回值:返回文件指针相对于起始位置的偏移量 例如: fseek(pf, 0, SEEK_END);//将文件指针置于文件末尾 int ch = ftell(pf);//返回文件指针相对于起始位置的偏移量 printf("%dn", ch); 可以很快知道文件的长度 3.rewind 函数原型:void rewind(FILE* pf) 函数功能:将文件指针重新定位与文件的开头 例如: fseek(pf, -2, SEEK_END);//将位置指针置于末尾向前偏移2字节的位置 int ch = ftell(pf);//返回当前文件指针的位置 printf("%dn", ch); rewind(pf);//将文件指针置于文件的起始位置 printf("%dn", ftell(pf)); fclose(pf);//关闭文件 pf = NULL;//将pf指针置为空 9.strerror/perror函数
1.strerror: 函数原型:char* strerror(int errnum) 函数功能:提供系统错误消息或打印用户提供的错误消息 返回值:返回指向错误消息字符串的指针 例如: FILE* pf = fopen("text.txt", "r"); if (pf == NULL)//以只读的方式打开文件 { printf("%sn", strerror(errno));//失败的话则输出错误信息 return 0; } fclose(pf); pf = NULL; 2.perror: 函数原型:void perror(const char* str) 函数功能:打印错误信息 例如: FILE* pf = fopen("text.txt", "r"); if (pf == NULL)//若打开文件失败 { perror("error");//输出错误信息error:No such file or directory return 0; } fclose(pf); pf = NULL;10.ferror/feof函数
1.ferror: 函数原型:int error(FILE* pf) 函数功能:检测文件流上的错误 返回值:如果流上没有没有发生错误,返回0;否则返回非0. 2.feof 函数原型:int feof(FILE* pf) 函数功能:测试流上文件结束 返回值:读到文件末尾返回非0;否则返回0 *ferror和feof常用来判断文件是否正常结束: int main() { FILE* pf = fopen("text.txt", "r"); if (!pf) { perror("error");//打开文件失败返回输出错误信息 return 0; } int c = 0; while ((c = fgetc(pf)) != EOF); if (ferror(pf))//文件异常结束,返回非0 printf("文件读取错误结束n"); else if (feof(pf))//文件正常结束返回非0 printf("文件正常结束n"); fclose(pf); pf = NULL; }十、C语言预处理
1.源文件 --> 可执行文件
①源文件(.c)经过编译器编译、链接器链接生成目标文件(.obj)。
②编译的过程又可以分为:预编译/预处理、编译、汇编。
预处理阶段进行的操作有引用包含文件(#include),替换#define定义的符号,注释删除(使用空格代替注释);编译阶段进行的操作有将C语言代码转换为汇编代码,发生语法分析,词法分析,语义分析,符号汇总;汇编阶段进行的操作有把汇编代码转化为二进制值指令,形成符号表;链接阶段进行的操作有合并段表(elf格式),符号表的合并和重定位。
③如图:
2.系统的预定义符号
①、__FILE__ //打印文件名称 ②、__LINE__ //打印当前行数 ③、__DATE__ //打印文件的编译日期 ④、__TIME__ //打印文件编译时间 ⑤、__STDC__ //如果编译器支持ANSI C,其值为1,否则未定义 例如: printf("%sn", __FILE__);//输出文件路径及名称 printf("%dn", __LINE__);//输出当前的行数 printf("%sn", __DATE__);//输出文件被编译的日期 printf("%sn", __TIME__);//输出文件被编译的时间 printf("%dn", __STDC__);//检查编译器是否遵循ANSC标准,如果遵循返回1,否则未定义3.#define定义的符号和宏
1.#define定义的符号 #define MAX 100 *在预处理阶段完成符号的替换,#define在定义符号时,最好不要在值的末尾加上分号 2.#define定义的宏 #define NAME(LIST) STUFF 例如: #define SQUARE(X) ((X)*(X)) 在预编译阶段,会用((X)*(X))来替换,加上括号的原因是考虑到运算符的优先级可能会导致运算出错,比如: #define SQUARE(X) X*X int a = SQUARE(5+1); 则a的值为11,这是因为在与编译阶段会用5+1替换X出现5+1*5+1,导致结果出错,因此选择对宏的每一个参数都加上括号。 再比如: #define DOUBLE(X) (X)+(X) int a = 10 * DOUBLE(5); 在运算时会出现10*5+5=55而导致结果出错,因此对于#define定义的宏最好在值的外侧在加上一层括号确保运算顺序的正确。即: #define SQUARE(X) ((X)+(X))最好4.#define里边的#和##
1.# 功能:将宏的参数插入到字符串中 例如: #include#define PRINT(X) printf("the value of "#X " is %dn",X)//在预处理阶段将#X替换为字符串"X" int main() { int a = 5; int b = 6; PRINT(a);//打印结果为the value of a is 5 PRINT(b);//打印结果为the value of b is 6 return 0; } 2.## 功能:把位于##两边的符号合成一个符号 例如: #define BAIXIAOSHUAI(X,Y) X##Y int main() { int ClassWin = 888; printf("%dn", BAIXIAOSHUAI(Class, Win));//将Class,Win连接成一个符号 return 0; } 5.#undef移除宏定义 条件编译#ifdef/#endif 避免头文件被多次包含 模拟实现offsetof
1.undef 功能:移除宏定义 例如: #define MAX 20 int main() { printf("%dn", MAX); #undef MAX//取消了MAX的定义,下一行无法打印 printf("%dn", MAX);//"MAX"未声明的标识符 return 0; } 2.条件编译避免头文件被多次包含 ① #ifdef __YUAN_H__ #define __YUAN_H__ 1 #include"yuan.h" #endif // __YUAN_H__ ② #pragma once #include"yuan.h" 3.模拟实现offsetof(计算偏移量)的宏 #define Offsetof(struct_type,struct_mem_name) (int)&(((struct_type*)0)->struct_mem_name) struct S { int a; char b; int c; }; int main() { printf("%dn", Offsetof(struct S, c)); return 0; }



