C++八股文-01
`
文章目录
前言
本系列主要的目的是找工作,要记!里面的内容大部分来源于网络,以及自己的一些理解和总结!如果有错误的地方,希望各位大佬指正!
(听说点赞收藏+关注的人都会进大厂噢!加油!!)
一、new/delete和malloc/free的区别
malloc的全称是memory allocation,中文叫动态内存分配
- malloc/free是C/C++的库函数,需要stdlib.h;new/delete是C++的关键字;
//C++
int *p;
p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);
或:
int* parr;
parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
//C
<span style="font-size:18px;">int* p;
p = (int *)malloc (sizeof(int)*128);
//分配128个(可根据实际需要替换该数值)整型存储单元,
//并将这128个连续的整型存储单元的首地址存储到指针变量p中
double *pd=(double *)malloc (sizeof(double)*12);
- 都可用于申请动态内存和释放内存,new/delete在对象创建的时候自动执行构造函数,对象消亡前自动执行析构函数,底层实现其实也是malloc/free,malloc申请的内存不再使用时,应使用free()函数将内存块释放。
- new无需指定内存块的大小,编译器会根据类型信息自行计算;malloc需要显式地支持所需内存的大小
- new返回指定类型的指针,无需进行类型转换;malloc默认返回类型为void*,必须强行转换为实际类型的指针(void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。)
- new内存分配失败时会抛出bad_alloc异常;malloc失败时返回NULL(空,什么也没有)
malloc的底层实现
Linux下:
- 开辟空间小于128K时,通过**brk()**函数
- 将数据段.data的最高地址指针_edata向高地址移动,即增加堆的有效区域来申请内存空间
- brk分配的内存需要等到高地址内存释放以后才能释放,这也是内存碎片产生的原因
- 开辟空间大于128K时,通过**mmap()**函数
- 利用mmap系统调用,在堆和栈之间文件映射区域申请一块虚拟内存
- 128K限制可由M_MMAP_THRESHOLD选项进行修改
- mmap分配的内存可以单独释放
- 以上只涉及虚拟内存的分配,直到进程第一次访问其地址时,才会通过缺页中断机制分配到物理页中
二、指针和引用的异同点;如何相互转换
- 本质:
引用
是(某块内存的)别名,而指针
是地址(是一个实体,指向某一内存、它的内容是所指内存的地址)。两者都是地址概念,所以本身都会占用内存
。 - 指针在运行时可以改变所指向的值,而引用一旦与某个对象绑定之后就不再改变(指向的地址不能改变,但指向的内容可以改变)
- 指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值;因此指针可以改变指向的对象,而引用的对象不能修改
- 由于硬件通过地址访问内存位置,因此引用可以理解为一个常量指针,只能绑定到初始化它的对象上
- 指针和引用的自增(++)运算符意义不同,指针是对内存地址自增,而引用是对值的自增。
- 引用使用时无需解引用(*),指针需要解引用;(解引用大家可以看看这篇博客,传送门)
- 引用不能为空,指针可以为空
- 引用没有const,指针有const;(指针有“指针常量”即int * const a,但是引用没有int& const a,不过引用有“常引用”即const int &a = 1)
- 指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。
- 引用转指针:把引用类型的对象用&取地址就获得指针了。
- p,*p和&p的区别(引入指针要注意程序中的p、*p 和 &p 三种表示方法的不同意义。设p为一个指针,则:)
p — 指针变量,它的内容是地址量
*p — 指针所指向的对象,它的内容是数据
&p — 指针变量占用的存储区域的地址,是个常量
int a = 50;
int *p ;
p = &a;
printf("&a = %p\n",&a); //取a的地址
printf("p = %p\n",p); //取指针p的值
printf("&p = %p\n\n",&p); //取指针p的地址
//由下面三时可以看出*p,a,*(&a)的关系
printf("*p = %d\n",*p);
printf("a = %d\n",a);
printf("*(&a) = %d\n",*(&a));
三、struct、union的异同
- 结构体 struct 中每个变量依次存储;共同体 union 中,每个变量都是从偏移地址零开始存储,同一时刻只有一个成员存储于该地址
- struct内存大小遵循结构对齐原则
- 数据成员对齐规则:每个数据成员存储的起始位置要从该成员大小的整数倍开始
- 数据成员包含结构体:结构体成员要从其内部最大元素对象的整数倍地址开 始存储
- 结构体总大小:其内部最大基本成员的整数倍,不足则要补齐
- union内存大小为其最大成员的整数倍
//
struct student_tag
{
int num; //包括一个int变量
char name[20]; //包括一个字符数组
char sex; //包括一个字符
int age; //包括一个int变量
float score; //包括一个float变量
char addr[30]; //包括一个字符数组
}; //最后有一个分号
union data
{
int i;
char ch;
float f;
}a,b;
四、extern C的作用
- C++支持函数重载,即不同名字空间namespace的两个函数原型声明可以完全相同,或者两个函数同名但参数列表不同;g++编译器会对此进行name mangling,生成全局唯一的符号名称,使链接器可以准确识别
- C语言不支持函数重载,即不允许同名符号,所以不需要这些工作,因此在C++代码中加入extern C,是为了链接规范
五、memcpy()函数需要注意哪些问题
函数原型声明void *memcpy(void *dest, void *src, unsigned int count);
memcpy函数用于把资源内存(src所指向的内存区域)中连续的count个字节数据拷贝到目标内存(dest所指向的内存区域)
- 数据长度count的单位是字节,1byte = 8bit
- 数据类型为char,则数据长度就等于元素的个数;其他数据类型则要注意数据长度的值
- n * sizeof(type_name)的写法
六、strcat、strncat、strcmp、strcpy函数
- strcpy拷贝函数,不会判断拷贝大小,也没有任何安全检查,不会检查目的地址内存是否够用;
- strncpy拷贝函数,会计算复制字符串的大小,但没有检查目标的边界;
- strcmp比较函数,把src所指向的字符串与dest所指向的字符串进行比较,若dest与src的前n个字符相同,则返回0;若dest大于src,则返回大于0的值;若dest小于src,则返回小于0的值
- strcat功能是将两个char类型连接;strncat功能是在字符串的结尾追加n个字符
七、机器大小端问题
- 大端指数据的高字节保存在内存的低地址中,数据的低字节保存在内存的高地址中;小端与此相反。
- 小端:强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样
- 大端:符号位的判定固定为第一个字节,很容易判断正负
union判断大小端的方法
union从低地址开始存,同一时间内只有一个成员占用内存;修改其中一个成员的值必然会影响另一个成员的值
八、static的用法(定义和用途)
- static修饰局部变量时:使其变为
静态存储方式
(静态数据区),函数执行完成之后不会被释放,而是继续保存在内存中; - static修饰全局变量时:使其只在本文件内部有效,其他文件不可链接或引用该变量;
- static修饰函数时:静态函数,即函数只在本文件内部有效,对其他文件不可见;避免同名干扰,同时保护
-
- 考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)
九、const的用法(定义和用途)
- const起到强制保护的修饰作用,可以预防意外改动,提高程序的健壮性
- const修饰常量:定义时就初始化,以后不能更改;
- const修饰形参:func(const int a); 该形参在函数里不能改变;
- const修饰类成员函数:const类成员函数不能改变成员变量的数值
十、const常量和宏定义#define的区别(编译阶段、安全性、内存占用等)
- const定义的常量有类型名字,存放在内存的静态区域中,在编译时确定其值;
- #define定义的常量是没有类型的一个立即数,编译器会在预处理阶段将程序中所有使用到该常量的地方进行拷贝替换;
- 由于#define的拷贝有很多份,故
宏定义的内存占用要高得多
- 定义域不同
//C
void f1 ()
{
#define N 12
const int n 12;
}
void f2 ()
{
cout<<N <<endl; //正确,N已经定义过,不受定义域限制
cout<<n <<endl; //错误,n定义域只在f1函数中
}
- 存储方式不同
宏定义是直接替换,不会分配内存,存储于程序的代码段中;
const常量需要进行内存分配,存储于程序的数据段中
(关于数据段以及代码段,传送门)
#define PI 3.14 //预处理后 占用代码段空间
const float PI=3.14; // 本质上还是一个 float,占用数据段空间
- #define 可以通过 #undef 取消某个符号的定义,再重新定义。const常量定义后将在定义域内永久有效
十一、volatile的用法
被定义为volatile的变量可能会被意想不到地改变,编译器不会对volatile变量有关的运算进行编译优化:每次使用该变量必须从内存地址中读取,而不是保存在寄存器中的备份
用到volatile的几种情况
- 并行设备的硬件寄存器(如状态寄存器)
- 中断服务子程序会访问到的非自动变量
- 多线程应用中被几个任务共享的变量
十二、常量指针、指针常量、常量引用(没有引用常量)
常量指针即常量的指针,指针所指向的是个常量,可以被赋值为变量的地址,但是不能通过这个指针来修改
指针常量本质是一个常量,指针所指向的值不可以改变,但指向的地址所对应的内容可以变化
char * const与const char *
const char * ptr 指向字符常量的指针(常量指针),ptr是一个char*类型的常量,所指向的内容不能修改;
char * const ptr 指向字符的指针常数(指针常量),即const指针,不能修改ptr指针,但可以修改该指针指向的内容
- 普通常量 :声明的同时必须初始化。变量声明之后,无法再修改改变量的值
const int var = 1024;
- 常量指针 :声明的时候不必初始化,可以更改指针指向,但
不能通过解引用来修改指针指向的数据
两种等价的声明方式
const int *p;
int const *p;
int a = 1024;
int b = 10;
const int *p;
p = &a;
p = &b;
//但是不能*p = 10;修改指针所指向内容的数据
- 指针常量
声明方式,必须在声明时初始化。
int * const p = &a;
可以通过解引用来修改指针指向的数据,但不能改变指针指向
int a = 1024;
int * const p = &a;
*p = 10;
- 常量引用 :不能修改引用的指向,也不能修改引用指向的值。
int a = 12;
const int& p = a;