
一、基本数据类型的取值范围¶

符号位¶
存放 signed 类型的存储单元中,左边第一位表示符号位。如果该位为 0,表示该整数是一个正数;如果该位为 1,表示该整数是一个负数。
一个 32 位的整型变量,除去左边第一位符号位,剩下表示值的只有 31 个比特位。
补码¶
- 正数的补码是该数的二进制形式。
- 负数的补码需要通过以下几步获得:
- 先取得该数的绝对值的二进制形式
- 再将第 1 步的值按位取反
- 最后将第 2 步的值加 1


使用补码的好处:
二、数组与指针¶
定义指针变量¶
1 | 类型名 *指针变量名 |
取地址运算符和取值运算符¶
如果需要获取某个变量的地址,可以使用取地址运算符 &:
1 | char *pa = &a; |
如果需要访问指针变量指向的数据,可以使用取值运算符 *:
1 | printf("%c, %d\n", *pa, *pb); |
数组名的真实身份¶
数组名其实是数组第一个元素的地址!
指向数组的指针¶
如果用一个指针指向数组,应该怎么做呢?
1 | a = {0, 1, 2} |
指针的运算¶
- 当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第 n 个元素。
- 对比标准的下标法访问数组元素,这种使用指针进行间接访问的方法叫做指针法。
- 需要郑重强调的是:
p+1并不是简单地将地址加1而是指向数组的下一个元素。

指针++的坑和左值¶

指针和数组的区别¶
结论:数组名只是一个地址,而指针是一个左值。
指针数组和数组指针¶

指针数组¶

1 |
|
数组指针¶

1 |
|
array 表示的是什么?¶




void 指针和 NULL 指针¶
void 指针¶
void 指针我们把它称之为通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给 void 指针。
NULL 指针¶
1 |
NULL 不是 NUL:NULL 用于指针和对象,表示指向一个不被使用的地址; 而 \0 表示字符串的结尾。
养成良好的编程习惯
当你还不清楚要将指针初始化为什么地址时,请将它初始化 NUL;在对指针进行解引用时,先检查该指针是否为 NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。
void 指针示例:

指向指针的指针¶

指针数组和指向指针的指针:
-
至少有两个好处:
- 一避免重复分配内存
- 一只需要进行一处修改
-
代码的灵活性和安全性都有了显著地提高!
1 |
|
数组指针和二维数组¶
1 |
|
常量¶
- 常量应该是这样:
520,‘a’,3.14 - 或者这样:
# define PRICE 520# defineA 'a'# define PI 3.14
- 还可以使用
const关键字修饰:const int price = 520;const char a = 'a';const float pi 3.14;
指向常量的指针¶
- 指针可以修改为指向不同的常量;
- 指针可以修改为指向不同的变量;
- 可以通过解引用来读取指针指向的数据;
- 不可以通过解引用修改指针指向的数据。
常量指针¶
- 指向非常量的常量指针:
- 指针自身不可以被修改;
- 指针指向的值可以被修改;
- 指向常量的常量指针:
- 指针自身不可以被修改;
- 指针指向的值也不可以被修改。
指针函数¶
使用指针变量作为函数的返回值,就是指针函数。

三、零散的点¶
可变参数¶

变量 i 的作用域¶

全局变量¶
在函数里边定义的,我们叫局部变量;在函数外边定义的,我们叫外部变量,也叫全局变量。
有时候,我们可能需要在多个函数中使用共同的一个变量,那么就会用到全局变量。因为全局变量可以被本程序中其他函数所共用的。
如果不对全局变量进行初始化,那么它会自动初始化为 0。
如果在函数的内部存在一个与全局变量同名的局部变量,编译器并不会报错,而是再函数中屏蔽全局变量(也就是说在这个函数中,全局变量不起作用)。
不要大量的使用全局变量:
- 使用全局变量会使你的程序占用更多的内存,因为全局变量从被定义时候开始,直到程序退出才被释放;
- 污染命名空间,虽然局部变量会屏蔽全变量,但这样一来也会降低程序的可读性,人们往往很难一下子判断出每个变量的含义和作用范围;
- 提高了程序的耦合性,牵一发而动全身,时间久了,代码长了,你都不知道全局变量被哪些函数修改过。
定义和声明¶
- 当一个变量被定义的时候,编译器为变量申请内存空间并填充一些值;
- 当一个变量被声明的时候,编译器就知道该变量被定义在其他地方;
- 声明是通知编译器该变量名及相关的类型已存在,不需要再为此申请内存空间;
- 局部变量既是定义又是声明;
- 定义只能来一次,否则就叫做重复定义某个同名变量;而声明可以有很多次。
extern 关键字¶
用 extern 关键字告诉编译器:这个变量我在后边定义了,你先别急着报错!
链接属性¶

external(外部的):- 多个文件中声明的同名标识符表示同一个实体;
internal(内部的):- 单个文件中声明的同名标识符表示同一个实体;
none(无):- 声明的同名标识符被当作独立不同的实体;
只有具备文件作用域的标识符才能拥有 external 或 internal 的链接属性,其他作用域的标识符都是none 属性。
默认情况下,具备文件作用域的标识符拥有 external 属性。也就是说该标识符允许跨文件访问。对于 external 属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体。
生存期¶
C 语言的变量拥有两种生存期:
- 静态存储期 (static storage duration)
- 自动存储期 (automatic storage duration)
具有文件作用域的变量属于静态存储期,函数也属于静态存储期。属于静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放。
具有代码块作用域的变量一般情况下属于自动存储期。属于自动存储期的变量在代码块结束时将自动释放存储空间。
四、存储类型¶
存储类型其实是指存储变量值的内存类型,C 语言提供了 5 中不同的存储类型:
- auto
- register
- static
- extern
- typedef
自动变量 (auto)¶
在代码块中声明的变量默认的存储类型就是自动变量,使用关键字 auto 来描述。
由于这是默认的存储类型,所以不写 auto 是完全没问题的。
寄存器变量 (register)¶
将一个变量声明为寄存器变量,那么该变量就有可能被存放于 CPU 的寄存器中。
寄存器变量和自动变量在很多方面的是一样的,它们都拥有代码块作用域,自动存储期和空连接属性。
不过这里有一点需要注意的是:当你将变量声明为寄存器变量,那么你就没办法通过取址运算符获得该变量的地址。
static 和 extern¶
作用于文件作用域的 static 和 extern。
static 关键宇使得默认具有 external 链接属性的标识符变成 internal 链接属性。
而 extern 关键字是用于告诉编译器这个变量或函数在别的地方已经定义过了,先去别的地方找找,不要急着报错。
五、内存空间管理¶
更灵活的内存管理方式:
malloc:申请动态内存空间;free:释放动态内存空间;calloc:申请并初始化一系列内存空间;realloc:重新分配内存空间。
malloc¶
函数原型:void *malloc(size_ size);
malloc 函数向系统申请分配 size 个字节的内存空间,并返回一个指向这块空间的指针。
如果函数调用成功,返回一个指向申请的内存空间的指针,由于返回类型是 void 指针 (void*),所以它可以被转换成任何类型的数据;如果函数调用失败,返回值是 NULL。另外,如果 size 参数设置为 0,返回值也可能是 NULL,但这并不意味着函数调用失败。
free¶
函数原型:void free(void *ptr);
free 函数释放 ptr 参数指向的内存空间。该内存空间必须是由 malloc、calloc 或 realloc 函数申请的。否则,该函数将导致未定义行为。如果 ptr 参数是 nLL,则不执行任何操作。注意:该函数并不会修改 ptr 参数的值,所以调用后它仍然指向原来的地方 (变为非法空间)。
malloc 和 free 的基本使用方法¶
1 |
|
malloc 还可以申请一块任意尺寸的内存空间:
1 |
|
初始化内存空间¶
以 mem 开头的函数被编入字符串标准库,函数的声明包含在 string.h 这个头文件中:
memset:使用一个常量字节填充内存空间;memcpy:拷贝内存空间;memmove:拷贝内存空间;memcmp:比较内存空间;memchr:在内存空间中搜索一个字符。
申请内存空间后使用 memset 进行初始化。
1 |
|
也可以使用 calloc 将两步操作合并执行。
calloc¶
函数原型:void*calloc(size_t nmemb, size_t size);
calloc 函数在内存中动态地申请 nmemb 个长度为 size 的连续内存空间 (即申请的总空间尺寸为 nmemb* size),这些内存空间全部被初始化为 0。
calloc 函数与 malloc 函数的一个重要区别是:
- calloc 函数在申请完内存后,自动初始化该内存空间为零
- malloc 函数不进行初始化操作,里边数据是随机的。
内存拷贝¶
1 |
|
也可以使用 reallor 将两步操作合并执行。
reallor¶
函数原型:void *realloc(void *ptr, size_t size);
以下几点是需要注意的:
- realloc 函数修改 ptr 指向的内存空间大小为 size 字节;
- 如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变; 如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,慎用!;
- 该函数将移动内存空间的数据并返回新的指针;
- 如果 ptr 参数为 NULL,那么调用该函数就相当于调用
malloc(size); - 如果 size 参数为 0,并且 ptr 参数不为 NULL,那么调用该函数就相当于调用
free(ptr); - 除非 ptr 参数为 NULL,否则 ptr 的值必须由先前调用 malloc、calloc 或 realloc 函数返回。
1 |
|
内存泄露¶
导致内存泄漏主要有两种情况:
- 隐式内存泄漏(即用完内存块没有及时使用 free 函数释放);
- 丢失内存块地址。
隐式内存泄露¶
1 |
|
丢失内存块地址¶
1 |
|
C 语言的内存分布规律¶

1 |
|

代码段¶
代码段(Text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定, 并且内存区域通常属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
数据段¶
数据段(Initialized data segment)通常用来存放已经初始化的全局变量和局部静态变量。
BBS 段(Bss segment/Uninitialized data segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS 是英文 Block Started by Symbol 的简称,这个区段中的数据在程序运行前将被自动初始化为数字 0。
堆¶
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。当进程调用 ma1loc 等函数分配内存时,新分配的内存就被动态添加到堆上;当利用 free 等函数释放内存时,被释放的内存从堆中被剔除。
栈¶
大家平时可能经常听到堆栈这个词,一般指的就是这个栈。栈是函数执行的内存区域,通常和堆共享同一片区域。
堆和栈的区别¶
-
申请方式:
- 堆由程序员手动申请;
- 栈由系统自动分配。
-
释放方式:
- 堆由程序员手动释放;
- 栈由系统自动释放。
-
生存周期:
- 堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问;
- 栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问。
-
发展方向:
- 堆和其它区段一样,都是从低地址向高地址发展;
- 栈则相反,是由高地址向低地址发展。
六、宏定义¶
不带参数的宏定义¶
1 |
- 为了和普通的变量进行区分,宏的名字通常我们约定是全部由大写字母组成;
- 宏定义只是简单地进行替换,并且由于预处理是在编译之前进行,而编译工作的任务之一就是语法检查,所以编译器不会对宏定义进行语法检查;
- 宏定义不是说明或语句,在末尾不必加分号;
- 宏定义的作用域是从定义的位置开始到整个程序结束;
- 可以用
#undef来终止宏定义的作用域宏定义允许嵌套。
带参数的宏定义¶
1 |
下面这样写是错误的!
1 |
七、结构体¶
结构体声明¶
1 | struct 结构体名称 |
定义结构体类型变量¶
1 | struct 结构体名称 结构体变量名 |
结构体在声明时并不会占用内存,当被真正定义时才会申请内存空间。
访问结构体变量¶
要访问结构体成员,我们需要引入一个新的运算符 – 点号 . 运算符。
比如 book.title 就是引用 book 结构体 title 的成员,它是一个字符数组;而 book.priceprice 则是引用 book 结构体的成员,它是一个浮点型的变量。
初始化结构体变量¶
初始化一个变量和数组:
1 | int a = 520; |
初始化一个结构体变量:
1 | struct Book book = { |
初始结构体的指定成员值¶
其语法和数组指定初始化元素类似,不过结构体指定初始化成员使用点号 . 运算符和成员名。
比如我们可以让程序只初始化Book的price成员:
1 | struct Book book = {.price = 48.8}; |
还可以不按结构体声明的成员顺序进行初始化:
1 | struct Book book = { |
内存对齐¶
struct 中会有内存对齐,存在比如每个成员的起始地址必须是其大小的倍数,整个 struct 大小是最大成员的倍数等规则。这其中的原因是什么呢?
结构体数组¶
第一种方法是在声明结构体的时候进行定义:
1 | struct 结构体名称 |
第二种方法是先声明一个结构体类型(比如上面 Book),再用此类型定义一个结构体数组:
1 | struct 结构体名称 |
初始化结构体数组¶
1 | struct Book book[3] = { |
结构体指针¶
1 | struct Book *pt // 声明 |
通过结构体指针访问结构体成员有两种方法:
(*结构体指针).成员名:用于结构体对象;结构体指针->成员名:用于指针。
八、二进制转换公式¶





