C语言常用知识点总结(重要)
指针与数组,指针与函数?
数组是用于存储多个相同类型的集合
指针是变量,存放的是其他变量在内存中的地址
区别:
赋值:数组不可以直接相互复制,只能数组中的每个元素依次赋值或者拷贝
指针可以相互赋值
表示范围:数组的有效范围就是某个空间的范围
指针可以指向任意地址
sizeof单目运算符:数组所占的内存大小:sizeof(数组名)
数组的大小:sizeof(数组名)/sizeof(数据类型)
指针所有的sizeof 对于32位平台:均占有四个字节
对于64位平台:均占有八个字节
指针数组与数组指针
指针数组:是一个数组,数组的元素是指针。int *qishou[2] 指针数组
数组指针:是一个指针变量,指针变量指向数组。int (*p)[3] 数组指针
对于数组指针的访问形式1.数组法 ; 2.指针法
数组法:*(p)[j] 指针法:*((*p)+j)
C语言将数组进行传参时将进行退化
在传递整个数组作为函数参数时,将数组名看作是常量指针
局部变量和全局变量是否可以重名?
可以,作用域的问题,
但是如果是在C++里面使用全局变量需要使用"::"来区分
如何引用一个已经定义过的全局变量?
已经定义的全局变量使用extern关键字来进行声明
全局变量和静态全局变量的区别?
static把全局变量的作用域减少了
当一个源程序由多个源文件组成时,非静态全局变量对各个源文件有效,静态成员变量则仅限于同一个源文件内。
Const和Define的区别?
编译器处理的方式不同:
define是在预处理阶段展开
const常量是在编译运行阶段使用
类型和安全检查(const的安全性更高)
define宏没有类型,当然也不做类型检查,define在预编译阶段就已经完成替换,所以也后续不能进行调试
const有具体的类型,执行更严格的类型检查,此外const也可以调试,
const修饰的变量存放在只读存储区,可以节省开销
常量折叠
存储方式不同
宏仅是展开,不分配内存
const在程序运行过程中有一次备份,占用数据段
指针和数组在strlen和sizeof中的大小计算?
strlen()函数接收的参数是指针,计算数组长度时不需要算上**‘\0’**,但是是需要遇到末尾的’\0’才结束
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//代表整个二维数组大小
printf("%d\n", sizeof(a[0][0]));//代表第一行第一个元素
printf("%d\n", sizeof(a[0]));//代表第一行元素总大小
printf("%d\n", sizeof(a[0]+1));//代表第一行第一个元素的地址跳过一个整型后的地址
printf("%d\n", sizeof(*(a[0]+1)));//代表第一行第二个元素解引用后的类型大小
printf("%d\n", sizeof(a+1));//代表首元素地址(即第一行地址) + 1 - 即第二行地址
printf("%d\n", sizeof(*(a + 1)));//代表*(a+1) <=> a[1],第二行元素总大小
printf("%d\n", sizeof(&a[0]+1));//&a[0]代表取出第一行的地址,+1代表取出第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));//解引用,计算的是第二行
printf("%d\n", sizeof(*a));//代表对第一行地址解引用,计算的是第一行元素总大小
printf("%d\n", sizeof(a[3]));
return 0;
}
sizeof和strlen的区别
sizeof是C语言中的一个单目运算符,用来计算数据类型所占空间的大小,单位为字节;在编译时起作用而不是运行时,没有规定具体的类型
而strlen是一个函数,用来计算字符串长度。使用时需要引用头文件 #include<string.h>,标准库函数
指针变量所占的空间大小为四个字节(32位系统)或者八个字节(64位系统)
形参和实参的区别?
形参是函数定义时使用的参数
形参是在函数声明时或内部定义中出现的参数,接收函数执行时传递的实际值
形参在函数内部起作用,是局部变量
形参仅在函数调用时才分配内存,函数执行完毕后,形参的内存才被释放
形参在调用时系统在栈上为其分配内存空间,函数执行完毕后会形参的内存空间会自动释放。
实参是函数调用时传递的参数
实参是函数调用时提供的值或者变量,他们的值会直接传递给函数的形参
实参可以是常量、变量、表达式、函数变量、地址等
实参在函数调用时被传递给形参,用于函数的具体执行
函数在进行参数传递时的多种方式?
值传递:
实参的值被赋值到形参的内存空间,但是可能导致内存消耗过大,需要额外的内存存储参数的副本,不能直接修改原始值
指针传递:
可直接修改原始值,避免大参数传递时创建参数副本的开销
需要手动管理内存分配和释放内存空间
可能为空或者指向无效的地址,需要进行有效性检查
手动管理内存可能出现的问题:
1.内存的泄漏
2.同一内存的重复释放
3.悬空指针:为将指针指向NULL或者指向了已经释放的内存
4.非法访问:指针访问已经释放或者未分配的内存区域
这里C++可以使用智能指针的方式解决
引用传递
引用传递无法传递空值
引用不能是空引用
数组、指针、地址之间的关系?
多个数组元素组成,元素按类型不同所占的连续内存大小不相同,一个数组的元素首地址就是所占连续内存的首地址
void指针和空指针的区别?
void指针是通用的指针类型,可以指向任何数据,不能直接解引用
提供一种通用方式来处理不同类型的指针
void指针的通用性
void指针的安全性:类型安全,不能直接算术运算,不能直接解引用
void指针的内存管理:动态内存分配中,void* 经常表示分配的内存块的地址。
NULL空指针:指向空地址的指针,空间在C/C++中主要用于初始化指针或者作为函数的参数表示无效的指针。
常用到NULL指针的地方:指针初始化,函数参数,返回值,指针检查。数据结构的终止条件、动态内存分配失败的处理。
关键字static的作用是什么?
关键字的作用有以下三个
-
在函数体内,静态局部变量
-
在模块内,函数体外,被声明为静态的变量可以被模块内所有的函数访问,但不能被模块外其他函数访问,这里属于是一个本地的全局变量
-
在模块内,一个被声明为静态的函数只可能被这一模块的其他函数调用。那就是,这个函数被限制在声明他的模块的本地范围内使用
-
这里延申到函数的作用域的问题,主要需要考察的点应该是 本地化数据和代码范围的好处和重要性。
-
static常被用来控制变量的存储方式和作用范围。
指针和引用的区别?
-
引用必须被初始化、指针不必
-
引用初始化以后不能被改变,指针可以改变所指向的对象
-
引用不能指向空值,指针可以
-
引用可以解决函数指针传递时手动进行动态内存分配可能带来的内存问题
-
指针通过某个指针变量指向某个对象后,对他所指向的变量进行间接操作,程序中使用指针,程序的可读性变差
-
引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
-
-
在内存中存储的区域可以分为,数据段、代码段、堆区、栈区
-
这里将内存中的数据分为,堆区,栈区,以及静态区数据,栈区存放临时的变量,例如局部变量;堆区主要进行内存的动态分配(malloc,calloc),然后是静态区存放静态变量和全局变量,使用static关键是修饰的变量都存储在静态区,放在静态区的数据直至程序结束才释放。
-
static指定变量只初始化一次;如果没有赋值,默认赋值为0;
-
static修饰局部变量时,会改变局部变量的存储位置,使局部变量的生命周期变长。
-
存储在静态数据区的变量会在程序刚开始运行时完成初始化,也是唯一的一处初始化。
头文件中的ifndef/define/endif有什么作用?
避免头文件的重定义,主要用于防止重复定义宏和重复包含头文件
有些头文件的重复引用:仅是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些
但是对于大工程编译,并不是影响编译效率这么简单,会引起错误,比如在头文件中定义了全局变量(虽然这种方式不被推荐,但确实在C规范中是允许的),这种方式会引起重复定义。
#include<file.h>和#include"file.h"的区别?
双引号:先在程序的源文件所在的目录查找,如果未找到则去系统的默认目录查找,通常用于包括程序作者编写的头文件。
尖括号:直接到系统默认目录或者是括号内的路径查找,通常包括标准库头文件、系统文件。编译器从标准库目录开始搜索(包括编译器设置的路径)
逻辑移位、算数移位、循环移位?
逻辑左移:高位舍弃,低位补0
逻辑右移:低位舍弃,高位补0
循环移位:循环移位的特点就是不舍弃数据,循环移位可以通过逻辑移位实现
若要实现循环移位,比如先要实现循环左移,对于一个32位数据,先右移32-n位数据,取
有符号移位
有符号右移:正数右移,左侧补0;负数右移,左侧补1;
还有个无符号移位,但是用得比较少 >>>
带进位的循环左移和右移
带进位的循环左移RCL(Rotate Left Through Carry):用原CF的值填补空出的位,移出的位再进入CF
导致堆栈溢出可能的原因?
-
一般导致堆栈溢出可能有五点原因
- 函数递归层次太深,递归函数在运行时会执行压栈操作,当压栈次数太多时也会导致堆栈溢出
- 局部变量的体积太大,因为局部变量是存储在栈内的
- 动态申请空间使用后没有释放(动态申请内存后不手动释放内存就会导致内存泄漏)
- 数组访问越界
- 指针非法访问。指针保存了一个非法地址,在通过指针访问所指向的地址的时候就会出现内存错误。
switch()的参数不能是实型。
如何引用一个已经定义的全局变量?
可以用引用头文件的方式,也可以用extern关键字
如果用引用头文件方式来引用某个在头文件中声明的全局变量,如果变量写错了,会在编译阶段报错
如果使用extern引用,在编译期间不会报错,而是在链接期间报错
程序中的内存分配?
一个由C/C++编译的程序占用的内存分为以下几个部分
-
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量值等,操作方式类似于数据结构中的栈。
-
堆区
程序员分配释放,一般都必须由程序员分配释放,如果不释放,在程序结束时可能由操作系统回收。
-
全局区(静态区)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量存储在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放
-
文字常量区
常量字符串存储在文字常量区,程序结束后由系统释放
-
程序代码区
存放函数体二进制代码
什么是预编译?何时需要预编译?
预编译又被称为预处理,是做一些文本的替换工作,处理#开头的指令
比如拷贝#include的文件代码
#define宏定义的替换,条件编译等,就是为编译阶段做预备工作的阶段。
主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置
C编译系统在对程序进行通常的编译之前,先进行预处理,C提供的预处理功能有以下三种:1)宏定义 2)文件包含 3)条件编译
const关键字有什么含义?
使用const关键字 意味着“只读”
为什么如此看重const关键字呢
- 使用关键字可以让程序阅读者更加清晰
- 使用const关键字也许能产生更紧凑的代码
- 合理地使用关键字const可以使编译器自然地保护那些不希望改变的参数,防止无意的代码修改
常量指针、指针常量、指向常量的常指针。
关键字volatile有什么含义?(很重要)
volatile变量即表示变量可能会被意向不到地改变,这样编译器就不会去假设这个变量的值
volatile告知编译器不能进行编译优化v
优化器在用到这个变量使必须每次小心地读取这个变量的值,而不是使用保存在寄存器里的备份。
volatile变量的例子:
三个点:一个是硬件寄存器访问、二是多线程编程、三是中断服务程序
并行设备的硬件寄存器:(状态寄存器)
一个中断服务子程序会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
一个参数可以既可以是const还可以是volatile吗?解释为什么?
举个例子,只读的状态寄存器,volatile表示他可能被意想不到地改变,const表示程序不应该试图去修改它
一个指针可以是volatile吗?
可以的,举个例子,假设有一个环形缓冲区,主程序和中断服务程序都在使用这个缓冲区。中断服务程序会更新缓冲区指针(例如,指向缓冲区的写入位置)。而主程序会读取该缓冲区指针。为了确保主程序总是读取最新的缓冲区指针,需要将指针定义为volatile。
结构体和联合体有什么区别?
内存的分配方式以及他们的区别?
-
从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在
-
在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
-
从堆上分配,亦称为动态内存分配,程序员手动分配和释放,即动态内存的生存期由程序员决定。
const和#define相比有什么优点?
const的作用:定义常量、修饰函数的参数、修饰函数返回值
被const修饰的东西都受到强制保护,能够提升程序的健壮性
const常量有数据类型,而宏定义没有数据类型,宏定义的生效阶段仅在预编译阶段完成替换,不能进行调试
而const具有数据类型,编译器可以进行类型安全检查,也可以进行调试。
如何判断一段程序是C编译程序还是C++编译程序的?
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
用两个栈实现一个队列的功能?要求给出算法和思路。
中断是嵌入式系统的重要组成部分 很多编译器开发商提供一种扩展-让标准C支持中断----于是产生了一个新的关键字 __interrupt
Typedef和#define的区别?
Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字,也可以预处理器做类似的事。
Typedef可以实现
基本数据类型定义(基本数据类型取别名)
指针数据类型定义
typedef void (*display_f)(void);
用于定义一个函数指针类型display_f。具体来说, display_f是一个指向无返回值、无参数的函数的指针类型
typedef可以用于定义一个新的类型名display_t,可以用于将函数作为参数传递给其他函数,或者将函数作为返回值返回。
可以使用display_t类型来声明函数指针变量,并将其指向一个符合要求的函数。
定义语句的语法如下:
typedef 返回类型 (*指针变量名)(参数列表);
用户对象类型定义(自定义数据类型取别名)
函数类型定义()
typedef 关键字与宏定义的区别?
从功能上来看,typedef主要是为已经存在的关键字或者类型及其组合取一个我们容易识别的别名。在这一点上#define也可以实现,但初此之外#define还有其它用处,如果愿意的话可以使用它定义任何代码,这是typedef所不具备的。
从执行时间上看,对于#define定义的宏,其在预处理阶段就已经被替换,typedef定义的类型会在编译时处理。
从作用域上来讲,#define定义的宏没有作用域的限制,只要在使用前定义就可以,而typedef定义的别名是有作用域的。
从实现效果上来讲,typedef定义一个指针类型,然后使用该类型同时声明多个变量,而#define却不是这样的。
例如
typedef (int*) pType
pType a,b
这里的a和b都是指向整数的指针变量。
同样使用#define 其在预处理阶段进行文本替换
#define pType int*
pType a,b;
这里只有a是指向整数的指针变量,而b只是一个整形的变量
预编译阶段进行文本替换 int* a,b;
拆分为 int *a ;int b;
编程实现:一个单向链表,不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指向的节点?
解决思路:
将这个指针指向的next节点值拷贝到本节点,然后设置该指针的next指向更改为next->next,并随后删除原next指向的节点。