本文主要是介绍想要深入理解C语言,这些点你必须知道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
c语言在线书籍:54笨鸟
程序员工具整合网站:编程网
## C程序概述
### 一个由C/C++编译的程序占用的内存分为以下几个部分
1. 栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,链表的数据空间必须采用堆存储分配策。
3. 全局区(静态区)(static):全局变量和静态变量的存储是放在这一块的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。
4. 文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。
5. 程序代码区:存放函数体的二进制代码
### C语言程序代码优化方法
1. 选择合适的数据结构与算法;
2. 使用尽量小的数据类型;
3. 使用自加、自减指令;
4. 用移位实现乘除法运算;
5. 求余运算用&(如a=a%8改为a=a&7);
6. 平方运算用*(如a=pow(a,2.0)改为a=a*a);
7. 延时函数的自加改为自减;
8. switch语句中根据发生频率来进行case排序;
9. 减少运算的强度。
## C源文件和头文件
### `#include <>` 和 `#include " "`的区别
1. `#include <>` :到保存系统标准头文件的位置查找头文件。
2. `#include" "`:查找当前目录是否有指定名称的头文件,然后再从标准头文件目录中查找。
## 关键字
1. 数据类型(常用char, short, int, long, unsigned, float, double)
2. 运算和表达式(=, +, -, *, while, do-while, if, goto, switch-case)
3. 数据存储(auto,static ,extern,const,register,volatile,restricted)
4. 结构(struct, enum, union,typedef),
5. 位操作和逻辑运算(<<, >>, &, |, ~,^, &&),
6. 预处理(#define, #include, #error,#if…#elif…#else…#endif等),
7. 平台扩展关键字(__asm, __inline,__syscall)
### static关键字的作用
1. static修饰局部变量(静态局部变量)与普通局部变量相比:
- 静态局部变量作用域与连接属性,和普通局部变量一样
- 存储类:静态局部变量分配在data/bss段,普通局部变量在栈上
- 生命周期:因为存储类的不同,静态局部变量生命周期变长了,直到程序结束
所以当静态局部变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能够再对它进行访问,直到该函数被再次调用,并且值不变。
2. static修饰的全局变量(函数)与普通的相比:
- 存储类、生命周期、作用域都一样
- 差别在于:
static修饰的全局变量的连接属性是内连接,普通的是外连接
即:static修饰的全局变量不能给文件调用——这也是静态变量和全局变量的区别。
内部函数:只能被本文件中的其他函数调用。定义内部函数时,在函数名、函数类型前加static。
外部函数:可供其他文件调用。定义外部函数时,在函数首部左端加上extern。若定义函数时省略extern,则默认为外部函数。
### const关键字的作用
const 是定义只读变量的关键字,或者说 const 是定义常变量的关键字。
说 const 定义的是变量,但又相当于常量;说它定义的是常量,但又有变量的属性,所以叫常变量。用 const 定义常变量的方法很简单,就在通常定义变量时前面加 const 即可,如:
`const int a = 10;`
const 和变量类型 int 可以互换位置,二者是等价的,即上条语句等价于:
`int const a = 10;`
那么用 const 修饰后和未修饰前有什么区别呢?它们不都等于 10 吗?、
用 const 定义的变量的值是不允许改变的,即不允许给它重新赋值,即使是赋相同的值也不可以。所以说它定义的是只读变量。这也就意味着必须在定义的时候就给它赋出值。
如果定义的时候未初始化,我们知道,对于未初始化的局部变量,程序在执行的时候会自动把一个很小的负数存放进去。这样后面再给它赋出值的话就是“改变它的值”了,即发生语法错误。
const 作用:
(1)可以定义 const 常量,具有不可变性。
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除一些隐患。
(3)可以避免意义模糊的数字出现,同样可以很方便进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变。
(4)可以保护被修改的东西,防止意外的修改,增强程序的健壮性。
(5)可以节省空间,避免不必要的内存分配。
(6)为函数重载提供了一个参考
(7)提高效率
### const和define的区别
1. 数据类型:const修饰的变量有明确的类型,而宏没有明确的数据类型
2. 安全方面:const修饰的变量会被编译器检查,而宏没有安全检查
3. 内存分配:const修饰的变量只会在第一次赋值时分配内存,而宏是直接替换,每次替换后的变量都会分配内存
4. 作用场所:const修饰的变量作用在编译、运行的过程中,而宏作用在预编译中
5. 代码调试:const方便调试,而宏在预编译中进行所以没有办法进行调试。
### volatile关键字
嵌入式开发中,常用到volatile关键字,它是一个类型修饰符,含义为“易变的”。使用方式如下:
volatile char i;
这里使用volatile关键字定义了一个字符型的变量i,指出i是随时可能发生变化的,每次使用该变量时都必须从i的地址中读取。
由于内存的读/写速度远不及CPU中寄存器的读/写速度,为了提高数据信息的存取速度。
一方面在硬件上引入高速缓存Cache。
另一方面在软件上使用编译器对程序进行优化,将变量的值提前从内存读取到CPU的寄存器中,以后用到该变量时,直接从速度较快的寄存器中读取,这样有利于提高运算速度。
但同时也可能存在风险,如该变量在内存中的值有可能被程序的其他部分(如其他线程)修改或覆盖,而寄存器中存放的仍是之前的值,这就导致应用程序读取的值和实际变量值不一致;
也有可能是寄存器中的值发生了改变,而内存中该变量的值没有被修改,同样也会导致不一致的情况发生。
因此,为防止由于编译器对程序进行优化导致读取错误数据,使用 volatile关键词进行定义。
简单地说,使用volatile关键字就是不让编译器进行优化,即每次读取或者修改值时,都必须重新从内存中读取或者修改,而不是使用保存在寄存器的备份。
举个简单的例子:大学里的奖/助学金的发放一般都是直接转给学校,学校再发给每名学生,学校财务处都登记了每名学生的银行卡号,但不可避免地会有一些学生因各种原因丢失银行卡或不再使用这张银行卡,而没来得及去财务处重新登记,从而影响奖/助学金的发放,这里,学生就是变量的原始地址,而财务处的银行卡号就是变量在寄存器中的备份,使用 volatile关键字来定义学生这个变量,这样每次发放奖/助学金时都去找学生这个变量的原始地址,而不是直接转到财务处保存的银行卡上,进而避免错误的发生。
const关键字的含义为“只读”,volatile关键字的含义为“易变的”,但volatile关键字解释为“直接存取原始内存地址”更为合适,使用 volatile关键字定义变量后,该变量就不会因外因而发生变化了。一般来说,volatile 关键字常用在以下场合。
- 中断服务程序中修改的、供其他程序检测的变量需要使用volatile关键字。
- 多任务环境下各任务间共享的标志应添加 volatile关键字。
- 外设寄存器地址映射的硬件寄存器通常要用volatile关键字进行声明。
## 类型
### 谈谈c语⾔中有符号和⽆符号的区别?
有符号:数据的最⾼位为符号位,0表示正数,1表示负数
⽆符号:数据的最⾼位不是符号位,⽽是数据的⼀部分
### 数据的类型转换:数据类型自动转换规则:char→int→long→float→double
## 常量
### 整型常量的存储-8在内存中的存储形式是(A)
A. 1111111111111000
B. 1000000000001000
C. 0000000000001000
D. 1111111111110111
本题主要考查整型常量的存储方式。
整型数据在内存中是以二进制的形式存放的,数值是以补码表示的。一个正数的补码和其原码的形式相同,一个负数的补码是将该数绝对值的二进制形式按位取反再加1。
这里-8绝对值在内存中的存储形式为00000000 00000000 00000000 00001000。
将其取反操作得11111111 11111111 11111111 11110111。
取反后加1得:11111111 11111111 11111111 11111000
## 变量
### 变量的声明与定义有啥区别?
声明变量**不需要**建⽴存储空间, 变量的定义**需要**建⽴存储空间
### 变量的定义
变量定义的形式:
`[存储类型] [特征修饰] 数据类型 变量名`
存储类型:决定变量存储位置
特征修饰: 决定变量的特征属性
| 特征名称 | 含义描述 |
| ---- | ---- |
| `const` | 只读属性,不是常量 |
| `volatile` | |
数据类型: 决定变量的存储空间及数据范围
变量名:决定变量的引用标识
### 存储类型
auto 只能修饰局部变量,存在栈(使用时临时申请空间,不用时,会释放空间,随着函数结束而释放)
register 存在寄存器(cpu里,不在内存 )只能修饰整型的局部变量,不能是浮点型,也不能进行取地址操作(寄存器没有地址)
extern 告诉编译器 ,不用给这个变量申请空间,因为它在其它.c文件申请过了(定义过了)(extern引用不了static变量,只能引用全局变量,static变量只在本文件内有效,加了static就用不了extern了)
static 既可以修饰全局变量,也可以修饰局部变量,存在静态区(data段),这个变量本文件用
const 声明常量变量
volatile 声明变量可能被外部改变
typedef 数据类型重定义
变量的种类:
全局变量:定义在函数外部的变量,作用范围是整个程序。
局部变量:定义在函数内部的变量,作用范围仅限于定义它的函数内部
静态变量:static,static int a;定义一次就不再消失,会把上一次的值保存起来,下一次直接拿来用。
静态局部变量:在函数内定义的静态变量,不可以被外部函数调用
### 描述⼀下普通局部变量、普通全局变量、静态局部变量、静态全局变量的区别
普通局部变量: 存在栈区、不初始化内容随机、只在定义所在的复合语句中有效、符合语句结束变量空间释放
普通全局变量 :存在全局区、不初始化内容为0、进程结束空间才被释放,能被当前源⽂件或其他源⽂件使⽤,只是其他源⽂件使⽤的时候,记得使⽤extern修饰
静态局部变量: 存在全局区、不初始化内容为0、整个进程结束空间才被释放,只能在定义所在的复合语句中有效
静态全局变量 :存在全局区、不初始化内容为0、整个进程结束空间才被释放,只能被当前源⽂件使⽤
### 变量内存分配
静态内存分配:
编译时分配,包括:全局、静态全局、静态局部
动态内存分配:
运行时分配:
包括:栈(局部变量),堆,动态存储区,需要程序员去申请释放(C语言常用到的变量被动态地分配到内存当中:malloc,calloc,realloc,free函数)
const修饰的全局变量也储存在常量区;const修饰的局部变量依然在栈上。
```c
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main(int argc, char **argv)
{
int b; //栈
char s[] = "abc";
//栈 char *p2;
//栈 char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
```
## 数组
### 谈谈数组的特点
同⼀个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的
### 数组的分类
数组的分类主要是:静态数组、动态数组两类。
静态数组:类似int arr[5];在程序运⾏就确定了数组的⼤⼩,运⾏过程不能更改数组的⼤⼩。
动态数组:主要是在堆区申请的空间,数组的⼤⼩是在程序运⾏过程中确定,可以更改数组的⼤⼩。
### 描述⼀下⼀维数组的不初始化、部分初始化、完全初 始化的不同点
不初始化:如果是局部数组 数组元素的内容随机 如果是全局数组,数组的元素内容为0
部分初始化:未被初始化的部分⾃动补0
完全初始化:如果⼀个数组全部初始化 可以省略元素的个数 数组的⼤⼩由初始化的个数确定
### 谈谈你对⼆维数组在物理上以及逻辑上的数组维度理解
⼆维数组在逻辑上是⼆维的,在物理上是⼀维的
### 谈谈数组名作为类型、作为地址、对数组名取地址的 区别?
数组名作为类型:代表的是整个数组的⼤⼩
数组名作为地址:代表的是数组⾸元素的地址
对数组名取地址:代表的是数组的⾸地址
## 结构体
### 如何理解结构体的浅拷⻉与深拷⻉
当结构体中有指针成员的时候容易出现浅拷⻉与深拷⻉的问题。
浅拷⻉就是,两个结构体变量的指针成员指向同⼀块堆区空间,在各个结构体变量释放的时候会出现多次释放同⼀段堆区空间
深拷⻉就是,让两个结构体变量的指针成员分别指向不同的堆区空间,只是空间内容拷⻉⼀份,这样在各个结构体变量释放的时候就不会出现多次释放同⼀段堆区空间的问题
### 描述⼀下结构体对⻬规则
1. 数组成员对⻬规则。第⼀个数组成员应该放在offffset为0的地⽅,以后每个数组成员应该放在offffset 为min(当前成员的⼤⼩,#pargama pack(n))整数倍的地⽅开始(⽐如int在32位机器为4字节,#pargama pack(2),那么从2的倍数地⽅开始存储)。
2. 结构体总的⼤⼩,也就是sizeof的结果,必须是min(结构体内部最⼤成员,#pargama pack(n))的整数倍,不⾜要补⻬。
3. 结构体做为成员的对⻬规则。如果⼀个结构体B⾥嵌套另⼀个结构体A,还是以最⼤成员类型的⼤⼩对⻬,但是结构体A的起点为A内部最⼤成员的整数倍的地⽅。(struct B⾥存有struct A,A⾥有 char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对⻬规则仍 满⾜原则1、原则2。
## 共用体
### 结构体与共⽤体的区别是啥
结构体中的成员拥有独⽴的空间,共⽤体的成员共享同⼀块空间,但是每个共⽤体成员能访问共⽤区的空间⼤⼩是由成员⾃身的类型决定
## 宏
### 啥叫宏函数以及作⽤
在项⽬中,经常把⼀些短⼩⽽⼜频繁使⽤的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以调⾼程序的效率。
宏通过使⽤参数,可以创建外形和作⽤都与函数类似地类 函数宏(function-like macro). 宏的参数也⽤圆括号括起来,来保证宏函数的完整性。
## 函数
### 函数参数的传递方式有几种?
(1)两种:值传递、指针传递。
(2)严格来看,只有一种传递,值传递,指针传递也是按值传递的,复制的是地址。
C语言的参数传递有传值和传地址两种方式。传值的过程:
(1)形参与实参各占一个独立的存储空间。
(2)形参的存储空间是函数被调用时才分配的。调用开始,系统为行参开辟一个临时存储区,然后将各实参之值传递给行参,这时形参就得到了实参的值。
(3)函数返回时,临时存储区也被撤销。传值的特点:单向传递,即函数中对形参变量的操作不会影响到调用函数中的实参变量。
### 描述⼀下函数的定义与函数的声明的区别
函数定义:是指对函数功能的确⽴,包括指定函数名、函数类型、形参及其类型、函数体等,它是⼀个完整的、独⽴的函数单位。
函数的声明:是把函数的名字、函数类型以及形参的个数、类型和顺序(注意,不包括函数体)通知编译系统,以便在对包含函数调⽤的语句进⾏编译时,据此对其进⾏对照检查(例如函数名是否正确,实参与形参的类型和个数是否⼀致)
### 内部函数与外部函数
1、内部函数
用static声明,不能被外部函数调用。
2、外部函数
用extern声明,可以被外部函数(其他c文件)调用。
Note: 函数默认为extern。调用外部函数时,需要进行声明,一般用头文件声明。
### 内联函数的优缺点和适用场景是什么?
(1)优点:内联函数与宏定义一样会在原地展开,省去了函数调用开销,同时又能做类型检查。
(2)缺点:它会使程序的代码量增大,消耗更多内存空间。
(3)适用场景:函数体内没有循环(执行时间短)且代码简短(占用内存空间小)
## 指针
### 描述⼀下32位或64位平台下指针的⼤⼩
32位平台:任意类型的指针⼤⼩为4字节
64位平台:任意类型的指针⼤⼩为8字节
### 变量的指针:指的是变量的地址,指针就是地址
```c
int i = 10;
int *ip;
ip = &i; // ip加上运算符*后表示访问指针ip内存地址所存储的内容,等价于直接访问i。*表示访问内存地址存储的内容
int x, y;
int *pt = &x;
y = *pt + 5; // 等价于y = x + 5
```
指针数组和数组指针
优先级顺序:() > [] > *
`(*p)[n]`根据优先级,先看括号内,则p是一个指针,这个指针指向一个一维数组,即数组的指针,数组指针。
`*p[n]`根据优先级,先看[],这是一个数组,再结合*,表示数组的元素是指针类型的,指针的数组,即指针数组,只能存放地址
### 描述⼀下指针与指针变量的区别
指针:没存中每⼀个字节都会分配⼀个32位或64位的编号,这个编号就是地址, ⽽指针就是内存单元的编号。
指针变量:本质是变量 只是该变量存放的是空间的地址编号
### 如何理解指针作为函数参数的输⼊和输出特性
输⼊特性:主调函数分配空间 背调函数使⽤该空间
输出特性:被调⽤分配空间 主调函数使⽤该空间
### 哪些情况下会出现野指针
指针变量未初始化、指针释放后未为置空、指针操作超越变量作⽤域
### 描述⼀下指针数组的概念
指针数组本质是数组,只是数组的每个元素是⼀个指针(地址)
## 库函数
### 如何理解库函数
库是已经写好的、成熟的、可复⽤的代码。每个程序都需要依赖很多底层库,不可能每个⼈的代码从零开始编写代码,因此库的存在具有⾮常重要的意义。
在我们的开发的应⽤中经常有⼀些公共代码是需要反复使⽤的,就把这些代码编译为库⽂件。 库可以简单看成⼀组⽬标⽂件的集合,将这些⽬标⽂件经过压缩打包之后形成的⼀个⽂件。
像在Windows这样的平台上,最常⽤的c语⾔库是由集成按开发环境所附带的运⾏库,这些库⼀般由编译⼚商提供
### strcpy和memcpy区别?
(1)复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
(2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
(3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
### 在使⽤realloc给已分配的堆区空间追加空间时需要注意啥?
记得⽤指针变量保存realloc的返回值
## 文件
### ⽂件缓冲区刷新⽅式有⼏种
⾏刷新、满刷新、强制刷新、关闭刷新
### 谈谈⽂件的分类
⽂件分为⼆进制和⽂本⽂件
⼆进制⽂件基于值编码,需要根据具体的应⽤才能知道某个值具体的含义
⽂本⽂件基于字符编码,⼀个字节⼀个意思,可以通过记事本打开
## 对齐
对于对齐的理解,可以分类为如下说明。
基础数据类型:以默认的的长度对齐,如char以1字节对齐,short以2字节对齐等
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合体 :按其包含的长度最大的数据类型对齐。
结构体:结构体中每个数据类型都要对齐,结构体本身以内部最大数据类型长度对
## 嵌入式C语言启动过程
嵌入式c语言搭载微处理器,用于嵌入式系统的启动程序要能够对目标系统的硬件和数据进行初始化,因此,用户必须做特定的启动程序。
这篇关于想要深入理解C语言,这些点你必须知道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!