C 语言的一些注意事项(上)
欢迎关注我的 微信公众号:破壳Ai,分享最佳学习路径、教程和资源。成长路上,有我陪你。
1. printf() 为什么需要输出控制符?
输出控制符:%d、%c 等
答:计算机中任何信息都是以 0 1 0 1 组合形式存在的,对于同样的 0 1 组合信息,计算机不知道该组合数据是一个整数还是一个其他类型的数,所以必须有个控制符来告诉计算机。
2. 一维数组名是变量吗?
如:
int a[5];
int b[5] = {1, 3, 4, 5, 6};
a = b; // 错误!
答:一维数组名 a 是常量,它等于这个数组第一个元素的地址。
所以 a = b;
是错误的,因为常量不能被赋值。
其中a[0]、a[1] 元素等是变量。
3. 一维数组如何赋值?
- 定义的同时进行赋值,只有这样才能整体赋值
int a[] = {1, 3, 12, 5}; //4个元素
int b[8] = {1, 3, 12, 5}; //8个元素,后5个为0
- 先定义后赋值,必须逐个赋值了
int a[5];
for ( int i = 0; i < 5; i++ ) {
a[i] = i;
}
注意:这个性质与结构体相似,但是结构体可以通过强制类型转换实现后期整体赋值
一般情况:
typedef struct student {
char * name;
int grade;
int score;
} Student;
Student s1 = {"zhangsan", 5, 99};
先定义后赋值特殊处理办法:
Student s2;
s2 = (Student){"lisi", 4, 89};
4. 使用指针有什么好处?
指针即地址,地址即内存单元的编号,一字节为一单元。
对于 32 位机器,即 32 根地址总线,能够访问 2^32 = 4G 个地址单元,所以老电脑最大只能支持 4G 内存。
指针有什么好处:
- 数据结构中,需要利用指针,比如链表、树、图;所以学习指针是为了数据结构打基础;
- 指针可以快速地传递数据,并且节省内存。因为不用复制整个数据进行传输,而仅仅传输该数据的指针(地址)即可;
- 被调函数可以利用指针返回多个值,如果没有指针只能返回一个值给主调函数;
- 相较于字符数组,指针处理字符串更方便;
- 指针可以直接访问硬件。
5. 变量与 0 比较
- 整数 0
if ( p == 0 )
if ( p != 0 )
- bool 类型
if ( p )
if ( !p )
bool 类型的 True 和 False 在 C 语言中是通过 #define True 1
、#define False 0
的形式定义的。如果写成 if ( p == True )
就等价于 if ( p == 1 )
,这就出问题了,因为实际上 bool 类型的 True 的实际含义是「非0的所有值,而非仅仅指1」。
- 指针类型
if ( p == NULL )
if ( p != NULL )
虽然 NULL 值为 0,但是含义不同,NULL 表示的是内存单元的编号 0,即 0x0000000000000000 地址。
计算机规定了以 0 为编号的内存单元不可读、不可写。
6. 传统数组(静态数组)的缺陷
- 内存空间的分配和释放是系统控制的。函数运行期间,系统为该函数内部的数组分配空间,待该函数运行完毕时,系统释放该数组内存空间。
- 进而导致 A 函数一旦运行结束,那么其他 B 函数就无法使用 A 中的数组变量了,即静态数组无法跨函数使用。
- c11 标准前,数组定义时必须指定长度,而且在函数运行过程中无法修改数组长度。
为了消除以上的缺陷,引入了动态数组,即动态内存分配的数组。
7. 动态内存分配
- 什么叫动态内存分配
malloc() 函数原型:(void *)malloc( int len )
,表示向系统申请 len 个字节的内存空间,如果申请成功则返回第一个字节的地址,如果失败,则返回 NULL。
- 为什么要强制类型转换?
如:int * p = (int *)malloc(50);
,向系统申请50个字节的内存空间,malloc 函数返回第一个字节的地址,但是这个地址是无意义的,需要转换为相应数据类型的地址才可以使用。
换言之就是,malloc 返回第一个字节的地址,经过(int *)
强制类型转换后,返回的就是4个字节的地址,那么 p 指针变量,指向的就是这4个字节,而非一个字节。那么p + 1
就指向了第二个4字节。
如:double * p = (double *)malloc(50)
,将 malloc 返回的第一个字节地址转换为 double * 型的地址,即将第一个字节的地址转换为8个字节地址,那么p + 1
就指向了第二个8字节。
8. 内存释放
对于int * p = (int *)malloc(50);
语句,系统分配了两块内存,一块是动态分配的50个字节的内存,一块是静态分配的 p 变量本身的内存(64位系统占用8字节)。
动态内存需要程序员手动释放:free(p)
。静态内存只能由系统来释放,即 p 本身的内存只能在 p 变量所在的函数终止时由系统自动释放。
注意:函数运行中,free(p)
释放了 p 指向的那个地址的内存,然后系统会将该地址交给其他程序使用。但是 p 变量本身的内存仍然存在,那么通过 p 依然可以找到被释放的那个内存,这就存在安全隐患了。
所以,不能对一个地址使用两次free(p)
,否则会破坏其他程序。
最好这么使用:
free(p);
p = NULL; // 拴住野指针,NULL 就是那条链子
9. 内存的五个部分
-
静态存储区:
存储:全局变量、static 变量;
生命周期:由编译器在编译时分配内存,整个程序结束后销毁;
特点:编译时未赋值的变量系统会自动赋初值0(数值型变量)或空字符(字符变量);
-
栈:
变量:局部变量;
生命周期:函数结束后销毁;
特点:效率高,空间有限;
-
堆:
变量:由 malloc 系列函数或 new 操作符分内存的变量;
生命周期:程序员手动释放,由 free 或 delete 决定;
特点:使用灵活,空间比较大,但容易出错;
-
常量存储区:
如:char * s = “Hello World”;
特点:只读,无法修改;
-
程序代码区:
程序运行时的函数体的二进制代码;
10. static 修饰符
-
修饰全局变量 -> 静态全局变量:
内存中的位置:静态存储区,不变;
初始化:自动初始化为 0;
作用域:只限于声明该变量的文件,而普通全局变量可以被所有源程序共享;
-
修饰局部变量 -> 静态局部变量:
内存中的位置:由「栈」变为了「静态存储区」;
初始化:未经初始化情况下,由分配垃圾值 -> 自动初始化为 0;
作用域:仍然为函数内部;
生命周期:函数运行周期 -> 整个程序运行周期;
-
修饰函数 -> 静态函数:
这里的 static 的作用不是改变存储位置,而是同修饰全局变量一样,改变了函数的作用域,由所有源文件缩小为仅限于本文件。
静态函数又称为内部函数,其优点:不同人编写不同函数时,不用担心其他人编写的文件里有同名函数。
总结:static 用来表示不能被其他文件访问的全局变量和函数,将在栈中分配的局部变量变为静态存储区的变量。
欢迎关注我的 微信公众号:破壳Ai,分享最佳学习路径、教程和资源。成长路上,有我陪你。