1.程序的翻译环境&2.执行环境
C语言程序实现的两种环境:
第一步:翻译环境--使得源程序转换为机器可执行的机器指令
第二步:执行环境--实现可执行代码
3.详解:程序的编译和链接(翻译环境)
多个test.c文件,多个test.obj,生成一个test.exe
编译器介绍:
链接库:库文件里的库函数/第三方库
4.预处理符号详解
4-1内置的预处理符号
int main() { for (int i = 0; i < 10; i++) { printf("name:%s\tfile:%s \tline:%d \tdate:%s \ttime:%s \ti:%d\n",__func__,__FILE__, __LINE__, __DATE__, __TIME__); } return 0; }
5.预处理指令
5-1#define定义符号
1. #define NUM 100 2. #define STR "hello world"//字符串也可以使用预处理定义符号
5-2#define定义宏
#define MAX(x,y) ((x)>(y)?(x):(y)) int main() { int a = 10; int b = 20; int c = MAX(a, b); printf("%d\n", c); return 0; }
注意:
- #define定义符号和宏的时候不要带分号
- 参数列表的左括号必须和name紧邻(函数可以,宏不可以)
- 写宏的时候,对于参数不要吝啬括号
#define NUM 100;//错误用例1 #define DOUBLE (x) x*x//错误用例2和3
5-3#define替换规则
#define M 100 #define DOUBLE(x) ((x)+(x)) int main() { int a = DOUBLE(M); printf("%d\n", a); return 0; } //第一步:-替换M- int a=DOUBLE(100) //第二步:-替换X- #define DOUBLE(100) 200 //第三步:-替换DOUBLE(100)- int a=200;
6。#和##宏的妙用
6-1#
6-1-1例子1:单纯只是研究辅助打印的信息,没有考虑参数的类型
问:怎么把参数插入到一个字符串中?
想法2:函数 //void Print(int n) //{ // printf("the value of n is &d\n", n); //} //想法3:宏 //#define PRINT(N) printf("the value of N is %d\n",N)//想法3 //这个法子和想法2一样,字符串中的x都没法得到替换(字符串中的符号不会被直接替换) //int main() //{ // int a = 10; // //printf("the value of a if %d\n", a); // Print(a); // // int b = 20; // //:想法1:一个一个打 // //printf("the value of b is %d\n", b); // PRINT(b); // // // return 0; //} //想法4:(最满足用户的做法)# #define PRINT(N) printf("the value of "#N" is %d\n",N) int main() { //基石 printf("hello world\n"); printf("hello ""world\n"); int a = 10; PRINT(a); //等价于:printf("the value of ""a"" is %d\n",N); return 0; }
#的作用是把N 变成"N",N 变字符串N
6-1-2:考虑到传入的参数的类型 (这使得我想到函数重载)
#define PRINT(N) printf("the value of "#N" is %d\n",N) int main() { int a = 10; double pai = 3.14; PRINT(a); PRINT(pai); return 0; }
6-2##
作用:##可以把位于它两边的符号合成一个符号
它允许宏定义从分离的文本片段创建标识符
#define CAT(name,num) name##num int main() { int song100 = 105; printf("%d\n", CAT(song, 100)); //等价于printf("%d\n",song100) return 0; }
这里我想解释一下一个东西:
解释:先进行预处理(先合成了classi),再编译
6-3带有副作用的宏参数🌸
++在宏中的副作用
#define MAX(m,n) ((m)>(n)?(m):(n)) int main() { //int a = 0; //int b = a + 1; //b = a++;//带有副作用的语句 //带有副作用的宏参数 int a = 10; int b = 20; int c = MAX(a++, b++); //相当于int c=(a++)>(b++)?(a++):(b++); // 11 21 22 printf("%d\n", a);//11 printf("%d\n", b);//22 printf("%d\n", c);//21 return 0; }
原因:
- 宏的参数是不带计算的替换的(函数的参数是带计算拷贝的)
- 如果宏中有多份++就会执行多次
7.宏和函数的对比(蓝色标明考虑角度)
宏没有函数栈帧的开销,也没有了函数递归;
宏只是简单替换,没了类型检查,也产生了优先级和副作用,和无法调试的问题。
宏和函数的对比
宏的优点:
没有函数调用和函数返回的开销
宏的参数与类型无关
宏的缺点:
宏是没有办法调试的
宏在使用不当,可能会带来运算符优先级和++的副作用问题
宏是没办法递归的
8.条件编译
应用:stdio.h头文件中好多这种东西,你要看得懂
#define NUM 1 int main() { //#if-#else-#endif 分支的条件编译 #if 0 printf("hehe\n"); #else printf("haha\n"); #endif //#if-#elif-(#else)-#endif 多分支的条件编译 #if NUM==1 printf("1\n"); #elif NUM==2 printf("2\n"); #else printf("0\n"); #endif //判断是否#define符号的两种方法 //方法1: #if defined(NUM) printf("1\n"); #endif //方法2: #ifdef NUM printf("2\n"); #endif //判断是否#undefine符号的两种方法 //方法1: #if !defined(NUM) printf("1\n"); #endif //方法2: #ifndef NUM printf("2\n"); #endif return 0;
9.预处理指令#include
9-1#include<stdio.h>和#inlcude"stdio.h"的区别
查找策略:
#include“include”:先在源文件的目录中查找,没找到再去目标库里查找
#include<stdio.h>:直接去目标库里查找
所以你的#include<stdio.h>可以写成#include"stdio.h"
但是你的contact.c中不能把#include"conta
ct.h"写成#include<contact.h>
推荐:
引用自己定义的头文件使用"""
引用库里的头文件使用<>
9-2防止头文件被重复包含的两种方法:(写在头文件里的)
多次包含了头文件的危害:平添了几千行代码,使得编译器处理起来压力大
方法1:
//test.c #include<stdio.h> #include"stdio.h" #include<stdio.h> //test.h #ifndef __TEST_H__ #define __TEST_H__ #endif
方法2:
1. //test.c 2. #include<stdio.h> 3. #include<stdio.h>//无效,这一次头文件并没有被包含 4. 5. #test.h 6. #pragma once
10.面试题:宏实现offsetof
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明。
首先我们来看看offsetof:
作用:返回type类型的结构体中,member结构体变量的地址相对于结构体起始地址的偏移量 原型:size_ t offsetof(type,member) 头文件:#include<stddef.h> 第一个参数:type类型的结构体 第二个参数:结构体成员变量名memer 返回值:size_t,无符号整型,可使用%zd或%ud打印 单词;offset偏移 of offsetof也就是...的偏移量 例子: struct Str { char c; int i; char t; }; int main() { struct Str s={ 0 }; printf("%zd\n", offsetof(struct Str, c));//0 printf("%zd\n", offsetof(struct Str, i));//4 printf("%zd\n", offsetof(struct Str, t));//8 return 0; } struct Str类型的结构体的起始地址:&(s.c) 成员变量名为c的地址:&(s.c) 则成员变量为c的地址相对于结构体的起始地址的偏移量offset==&(s.c)-&(s.c);
这里我们假设其实地址就是0,偏移量就就是&(s.c)-0==&(s.c),也就是说每一个成员变量的地址就变成了偏移量。
使用宏实现offsetof:
struct Str { char c; int i; char t; }; #define OFFSETOF(type,member) (size_t)&(((type*)0)->member) int main() { struct Str s={0}; printf("%zd\n", OFFSETOF(struct Str, c)); printf("%zd\n", OFFSETOF(struct Str, i)); printf("%zd\n", OFFSETOF(struct Str, t)); return 0; }