Skip to content

Commit 2865f4c

Browse files
author
yangjingjing
committed
init blog
1 parent 4aef3a6 commit 2865f4c

File tree

37 files changed

+2817
-751
lines changed

37 files changed

+2817
-751
lines changed

_posts/2015-09-05-C语言NULL.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
layout: post
3+
categories: [C]
4+
description: none
5+
keywords: C
6+
---
7+
# C语言NULL
8+
在C语言中,如果一个指针不指向任何数据,我们就称之为空指针,用NULL表示。
9+
10+
## C语言NULL
11+
在 C 语言中,NULL 是一个预定义的宏,通常被定义为 (void *)0 或者是一个整数值0。一般定义在 stdio.h 或 stddef.h 头文件中,形如:
12+
```
13+
#define NULL ((void *)0)
14+
```
15+
16+
那么我们来看看什么是空指针?
17+
```
18+
If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
19+
```
20+
21+
NULL 主要用来表示一个指针变量没有指向任何有效地址,也就是空指针(null pointer)。空指针可以用于条件语句的判断,比如检查一个指针是否已经被赋值:
22+
```
23+
int *ptr = NULL;
24+
if (ptr == NULL) {
25+
/* 指针ptr未被赋值,是一个空指针 */
26+
}
27+
```
28+
29+
在实际开发中,程序员可以使用 NULL 作为空指针的初始化值,这样可以避免指针变量在未初始化时不确定的状态。同时,C 语言中的很多函数和库也使用 NULL 来表示一些特定的状态,比如文件结尾、查询失败等。
30+
31+
通常情况下,将未初始化的指针设置为 NULL,可以避免指针在使用之前指向一个未知的内存位置,从而避免出现访问非法内存地址的情况。
32+
33+
在使用指针之前,可以通过检查指针是否为 NULL,来避免出现悬空指针错误,例如在用malloc()函数动态创建数组时,可以通过返回值是不是NULL来判断空间分配是否成功:
34+
35+
```
36+
#include <stdio.h>
37+
#include <stdlib.h>
38+
39+
int main() {
40+
int *ptr = NULL;
41+
int size = 10;
42+
43+
ptr = (int*)malloc(size * sizeof(int));
44+
if (ptr == NULL) {
45+
printf("malloc failed!\n");
46+
return 1;
47+
}
48+
49+
free(ptr);
50+
51+
return 0;
52+
}
53+
```

_posts/2015-09-05-C语言宏.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
---
2+
layout: post
3+
categories: [C]
4+
description: none
5+
keywords: C
6+
---
7+
# C语言宏
8+
9+
10+
## 概念
11+
一种最简单的宏的形式如下:
12+
```
13+
一种最简单的宏的形式如下:
14+
#define 宏名 替换文本
15+
```
16+
每个#define行(即逻辑行)由三部分组成:第一部分是指令 #define 自身,“#”表示这是一条预处理命令,“define”为宏命令。
17+
18+
第二部分为宏(macro),一般为缩略语,其名称(宏名)一般大写,而且不能有空格,遵循C变量命令规则。“替换文本”可以是任意常数、表达式、字符串等。在预处理工作过程中,代码中所有出现的“宏名”,都会被“替换文本”替换。这个替换的过程被称为“宏代换”或“宏展开”(macro expansion)。“宏代换”是由预处理程序自动完成的。
19+
20+
在C语言中,“宏”分为两种:无参数 和 有参数。
21+
22+
## 常规宏定义
23+
在 C 语言中,可以采用命令 #define 来定义宏。该命令允许把一个名称指定成任何所需的文本,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义时指定的文本替换掉。
24+
25+
无参宏是指宏名之后不带参数,上面最简单的宏就是无参宏。
26+
```
27+
#define M 5
28+
#define PI 3.1415926
29+
#define ARRAY_SIZE 100
30+
#define TITLE "*** Examples of Macros Without Parameters ***"
31+
#define BUFFER_SIZE (4 * 512)
32+
#define RANDOM (-1.0 + 2.0*(double)rand() / RAND_MAX)
33+
```
34+
35+
注意宏不是语句,结尾不需要加“;”,否则会被替换进程序中,如:
36+
```
37+
#define N 10; // 宏定义
38+
int c[N]; // 会被替换为: int c[10;];
39+
//error:… main.c:133:11: Expected ']'
40+
```
41+
以上几个宏都是用来代表值,所以被成为类对象宏(object-like macro,还有类函数宏,下面会介绍)。
42+
43+
如果要写宏不止一行,则在结尾加反斜线符号使得多行能连接上,如:
44+
```
45+
#define HELLO "hello \
46+
the world"
47+
```
48+
注意第二行要对齐,否则,如:
49+
```
50+
#define HELLO "hello the wo\
51+
rld"
52+
printf("HELLO is %s\n", HELLO);
53+
//输出结果为: HELLO is hello the wo rld
54+
```
55+
也就是行与行之间的空格也会被作为替换文本的一部分
56+
57+
而且由这个例子也可以看出:宏名如果出现在源程序中的“”内,则不会被当做宏来进行宏代换。
58+
59+
宏可以嵌套,但不参与运算:
60+
```
61+
#define M 5 // 宏定义
62+
#define MM M * M // 宏的嵌套
63+
printf("MM = %d\n", MM); // MM 被替换为: MM = M * M, 然后又变成 MM = 5 * 5
64+
```
65+
宏代换的过程在上句已经结束,实际的 5 * 5 相乘过程则在编译阶段完成,而不是在预处理器工作阶段完成,所以宏不进行运算,它只是按照指令进行文字的替换操作。再强调下,宏进行简单的文本替换,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段发现。
66+
67+
宏定义必须写在函数之外,其作用域是 #define 开始,到源程序结束。如果要提前结束它的作用域则用 #undef 命令,如:
68+
```
69+
#define M 5 // 宏定义
70+
printf("M = %d\n", M); // 输出结果为: M = 5
71+
#define M 100 // 取消宏定义
72+
printf("M = %d\n", M); // error:… main.c:138:24: Use of undeclared identifier 'M'
73+
```
74+
也可以用宏定义表示数据类型,可以使代码简便:
75+
```
76+
#define STU struct Student // 宏定义STU
77+
struct Student{ // 定义结构体Student
78+
char *name;
79+
int sNo;
80+
};
81+
STU stu = {"Jack", 20}; // 被替换为:struct Student stu = {"Jack", 20};
82+
printf("name: %s, sNo: %d\n", stu.name, stu.sNo);
83+
```
84+
如果重复定义宏,则不同的编译器采用不同的重定义策略。有的编译器认为这是错误的,有的则只是提示警告。Xcode中采用第二种方式。如:
85+
```
86+
#define M 5 //宏定义
87+
#define M 100 //重定义,warning:… main.c:26:9: 'M' macro redefined
88+
```
89+
这些简单的宏主要被用来定义那些显式常量(Manifest Constants)(Stephen Prata,2004),而且会使得程序更加容易修改,特别是某一常量的值在程序中多次被用到的时候,只需要改动一个宏定义,则程序中所有出现该变量的值都可以被改变。而且宏定义还有更多其他优点,如使得程序更容易理解,可以控制条件编译等。
90+
91+
## 带参数的宏
92+
你可以定义具有形式参数(简称“形参”)的宏。当预处理器展开这类宏时,它先使用调用宏时指定的实际参数(简称“实参”)取代替换文本中对应的形参。带有形参的宏通常也称为类函数宏。
93+
94+
95+
当定义一个宏时,必须确保宏名称与左括号之间没有空白符。如果在名称后面有任何空白,那么命令就会把宏作为没有参数的宏,且从左括号开始采用替换文本。
96+
97+
C语言中宏是可以有参数的,这样的宏就成了外形与函数相似的类函数宏(function-like macro),如:
98+
```
99+
#define putchar(x) putc(x, stdout)
100+
```
101+
102+
宏调用:
103+
```
104+
宏名(实参表);
105+
```
106+
和函数类似,在宏定义中的参数成为形式参数,在宏调用中的参数成为实际参数。
107+
108+
而且和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参。如:
109+
```
110+
#define M 5 //无参宏
111+
#define COUNT(M) M * M //有参宏
112+
printf("COUNT = %d\n", COUNT(10)); // 替换为: COUNT(10) = 10 * 10
113+
// 输出结果: COUNT = 100
114+
```
115+
这看上去用法与函数调用类似,但实际上是有很大差别的。如:
116+
```
117+
#define COUNT(M) M * M //定义有参宏
118+
int x = 6;
119+
printf("COUNT = %d\n", COUNT(x + 1));// 输出结果: COUNT = 13
120+
printf("COUNT = %d\n", COUNT(++x)); // 输出结果: COUNT = 56
121+
```
122+
这两个结果和调用函数的方法的结果差别很大,因为如果是像函数那样的话,COUNT(x + 1)应该相当于COUNT(7),结果应该是 7 * 7 = 49,但输出结果却是21。
123+
124+
原因在于,预处理器不进行技术,只是进行字符串替换,而且也不会自动加上括号(),所以COUNT(x + 1)被替换为 COUNT(x + 1 * x + 1),代入 x = 6,即为 6 + 1 * 6 + 1 = 13。
125+
126+
而解决办法则是:尽量用括号把整个替换文本及其中的每个参数括起来:
127+
```
128+
#define COUNT(M) ((M) * (M))
129+
```
130+
但即使用括号,也不能解决上面例子的最后一个情况,COUNT(++x) 被替换为 ++x * ++x,即为 7 * 8 = 56,而不是想要 7 * 7 = 49,解决办法最简单的是:不要在有参宏用使用到“++”、“–”等。
131+
132+
上面说到宏名中不能有空格,宏名与形参表之间也不能有空格,而形参表中形参之间可以出现空格:
133+
```
134+
#define SUM (a,b) a + b //定义有参宏
135+
printf("SUM = %d\n", SUM(1,2)); //调用有参宏。Build Failed!
136+
因为 SUM 被替换为:(a,b) a + b
137+
```
138+
139+
如果用函数求一个整数的平方,则是:
140+
```
141+
int count(int x){
142+
return x * x;
143+
}
144+
```
145+
所以在宏定义中:#define COUNT(M) M * M 中的形参不分配内存单元,所以不作类型定义。而函数 int count(int x)中形参是局部变量,会在栈区分配内存单元,所以要作类型定义,而且实参与形参之间是“值传递”。而宏只是符号代换,不存在值传递。
146+
147+
宏定义也可以用来定义表达式或者多个语句。如:
148+
```
149+
#define JI(a,b) a = i + 3; b = j + 5; //宏定义多个语句
150+
int i = 5, j = 10;
151+
int m = 0, n = 0;
152+
JI(m, n); // 宏代换后为: m = i + 3, n = j + 5;
153+
printf("m = %d, n = %d\n", m, n); // 输出结果为: m = 8, n = 15
154+
```
155+
156+
## 只定义宏名
157+
```
158+
#define ISENABLE
159+
等价于:
160+
#define ISENABLE 1
161+
```
162+
163+
## 宏的作用范围
164+
在C/C++中,宏定义的有效范围被规定为当前文件内有效。
165+
166+
“当前文件内有效”分为两种情况,一种是定义在头文件中,另一种是定义在源文件中。
167+
168+
- 在头文件中的宏定义随着头文件一同被包含到源文件中时,此时宏定义在该源文件中有效,当头文件中的宏定义随着该头文件一起被包含到另一个头文件中,而这另一个头文件又被另一个源文件包含,则该宏定义在最终被包含的源文件中同样有效。
169+
170+
- 当宏定义定义在源文件中时,只在当前源文件中有效,即使当前源文件所对应的头文件被其它源文件包含,由于相应的头文件中不包含宏定义,其它源文件也不能直接使用该宏定义。(也就相当于文件内的私有成员,只能被文件内的成员使用)。
171+
172+
- 当然在该文件中有效的含义是在宏定义语句之后的部分,同一个文件中宏定义语句之前,使用该宏是无效的。
173+
174+
## 宏的注意事项
175+
宏定义后不加分号。
176+
177+
宏名称采用全大写。
178+
179+
宏定义不受函数和结构体限制,不管在结构体中还是函数中都是全局作用范围。
180+
181+
两个宏定义之间没有前后顺序要求。
182+
```
183+
// 以下宏定义合理
184+
#define A B+2
185+
#define B 3
186+
187+
int a = A+B;
188+
```
189+
190+
## #define 与 #typedef 的区别:
191+
两者都可以用来表示数据类型,如:
192+
```
193+
#define INT1 int
194+
typedef int INT2;
195+
```
196+
两者是等效的,调用也一样:
197+
```
198+
INT1 a1 = 3;
199+
INT2 a2 = 5;
200+
```
201+
但当如下使用时,问题就来了:
202+
```
203+
#define INT1 int *
204+
typedef int * INT2;
205+
INT1 a1, b1;
206+
INT2 a2, b2;
207+
b1 = &m; //... main.c:185:8: Incompatible pointer to integer conversion assigning to 'int' from 'int *'; remove &
208+
b2 = &n; // OK
209+
```
210+
因为 INT1 a1, b1; 被宏代换后为: int * a1, b1;即定义的是一个指向int型变量的指针 a1 和一个int型的变量b1.而INT2 a2, b2;表示定义的是两个变量a2和b2,这两个变量的类型都是INT2的,也就是int *的,所以两个都是指向int型变量的指针。
211+
212+
所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typede不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。
213+
214+
## 宏处理
215+
从开始写C语言到生成执行程序的流程大致如下(姑且忽略预处理之前的编译器的翻译处理流程等),在进行编译的第一次扫描(词法扫描和语法分析)之前,会有由预处理程序负责完成的预处理工作。
216+
217+
预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令 #include,宏命令 #define 等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。
218+
219+
## # 运算符
220+
比如如果我们宏定义了:
221+
```
222+
#define SUM (a,b) ((a) + (b))
223+
```
224+
225+
我们想要输出“1 + 2 + 3 + 4 = 10”,用以下方式显得比较麻烦,有重复代码,而且中间还有括号:
226+
```
227+
printf("(%d + %d) + (%d + %d) = %d\n", 1, 2, 3, 4, SUM(1 + 2, 3+ 4));
228+
```
229+
230+
那么这时可以考虑用 # 运算符来在字符串中包含宏参数,# 运算符的用处就是把语言符号转化为字符串。例如,如果 a 是一个宏的形参,则替换文本中的 #a 则被系统转化为 “a”。而这个转化的过程成为 “字符串化(stringizing)”。
231+
232+
用这个方法实现上面的要求:
233+
```
234+
#define SUM(a,b) printf(#a " + "#b" = %d\n",((a) + (b))) //宏定义,运用 # 运算符
235+
SUM(1 + 2, 3 + 4); //宏调用
236+
//输出结果:1 + 2 + 3 + 4 = 10
237+
```
238+
调用宏时,用 1 + 2 代替 a,用 3 + 4 代替b,则替换文本为:printf(“1 + 2” ” + ” “3 + 4” ” = %d\n”,((1 + 2) + (3 + 4))),接着字符串连接功能将四个相邻的字符串转换为一个字符串:
239+
```
240+
"1 + 2 + 3 + 4 = %d\n"
241+
```
242+
243+
## ## 运算符
244+
和 # 运算符一样,## 运算符也可以用在替换文本中,而它的作用是起到粘合的作用,即将两个语言符号组合成一个语言符号,所以又称为“预处理器的粘合剂(Preprocessor Glue)”。用法:
245+
```
246+
#define NAME(n) num ## n //宏定义,使用 ## 运算符
247+
int num0 = 10;
248+
printf("num0 = %d\n", NAME(0)); //宏调用
249+
```
250+
NAME(0)被替换为 num ## 0,被粘合为: num0。
251+
252+
## 可变宏:… 和 __VA_ARGS__
253+
我们经常要输出结果时要多次使用 prinf(“…”, …); 如果用上面例子#define SUM(a,b) printf(#a ” + “#b” = %d\n”,((a) + (b))),则格式比较固定,不能用于输出其他格式。
254+
255+
这时我们可以考虑用可变宏(Variadic Macros)。用法是:
256+
```
257+
#define PR(...) printf(__VA_ARGS__) //宏定义
258+
PR("hello\n"); //宏调用
259+
//输出结果:hello
260+
```
261+
在宏定义中,形参列表的最后一个参数为省略号“…”,而“__VA_ARGS__”就可以被用在替换文本中,来表示省略号“…”代表了什么。而上面例子宏代换之后为: printf(“hello\n”);
262+
263+
还有个例子如:
264+
```
265+
#define PR2(X, ...) printf("Message"#X":"__VA_ARGS__) //宏定义
266+
double msg = 10;
267+
PR2(1, "msg = %.2f\n", msg); //宏调用
268+
//输出结果:Message1:msg = 10.00
269+
```
270+
在宏调用中,X的值为10,所以 #X 被替换为”1”。宏代换后为:
271+
```
272+
printf("Message""1"":""msg = %.2f\n", msg);
273+
```
274+
275+
接着这4个字符串连接成一个:
276+
```
277+
printf("Message1:msg = %.2f\n", msg);
278+
```
279+
要注意的是:省略号“…”只能用来替换宏的形参列表中最后一个!
280+
281+
282+
283+
284+
285+
286+
287+
288+
289+
290+
291+
292+
293+
294+
295+
296+
297+
298+
299+
300+

0 commit comments

Comments
 (0)