cpp基础核心内容
三块核心内容
进程虚拟地址空间区域划分
有四区
- 代码区(.text) 存放程序的机器指令,通常是只读的
- 数据段(.data) 存放已初始化的全局变量和静态变量
- BSS段 存放未初始化的全局变量和静态变量,操作系统在运行前会帮助自动初始化为0
- 堆栈区(Heap and Stack)
- 堆区:程序运行时动态分配的内存(new 和 malloc),由程序员管理
- 栈区:函数调用时使用的内存,存放局部变量和返回地址,由系统自动分配和释放
1 | int main() |
这三条是其实是汇编的mov指令,存放在.text区,而main函数调用的时候会在栈开辟空间。
函数调用堆栈详细过程
程序编译链接原理
预处理-编译-汇编-链接
- 预处理 处理#include 之类的,除了#pragma lib和#pragma link,这两个是在链接时处理。
- 编译 gcc / g++(gcc用来编译c语言,g++用来编译c++,其实就是dev中的F9,只不过现在换成命令行形式) 在编译的过程中符号不分配虚拟地址,在链接分配。
- 汇编 符号表的生成,生成.o文件。.o文件为目标文件(Object File),目标文件是一个二进制文件,文件的格式是ELF(executable and linkable file)
- 链接 将各个段合并(比如main.o和sum.o)生成可执行文件,Linux下是.out/Windows下是.exe
- 步骤一:所有的.o文件段合并,符号表合并后,进行符号解析(要找到所有符号表引用的定义)。
- 步骤二:符号的重定向(让UND找到初始定义的位置)这是链接的核心,符号解析成功后,给所有的符号分配虚拟地址!
g++/gcc语法
- gcc -c / g++ -c 是编译 compile
使用objdump -t查看符号表(t - table)
使用cat查看文件内容 - 在main.cpp中,我们看的定义gdata是一个外部引用的变量,sum是声明,看main.o的符号表,他们并不是没有符号,符号是UND(undefined),这个意思是:我现在在当前代码上用到他们了,但是我却不知道他们是怎么定义的,UND是对于符号的引用,有确定数据段的是符号的定义需要在链接这一部分才能让他们找到“家”,main函数放在.text代码段上,我们定义了data,所以data放在了.data段
- 在sum.o中,因为sum.cpp定义好了每一个位置,所以符号表都有。
文件头,main.o 。 .o文件就是由各种各样的段来组成的
可以使用objdump -s xxx.o查看段的信息
ld是链接器,负责链接。 - ELF文件头记录着文件的入口点地址,程序就知道从第几行开始执行了。
总结 a.out和.o文件都是由各种段组成的,但区别在a.out多了一个#program headers段,有两个load,告诉程序在运行的时候需要加载哪些数据。
C++基础部分
形参带默认值的函数
1 | int sum(int a = 20, int b ) |
这个代码是错误的,因为在函数传参压入栈的时候,参数是由右往左压,现在b没有默认值,所以是错的,如果定义b为20,a不定义,代码是正确的。代码顺序:从上到下,从右到左给默认值
内联函数inline
/* inline内联函数和普通函数的区别???
- inline内联函数:在编译过程中,就没有函数的调用开销了,在函数的调用点直接把函数的代码进行展开处理
- inline函数不再生成相应的函数符号(objdump -t)
- inline只是建议编译器把这个函数处理成内联函数,但是不是所有的inline都会被编译器处理成内联函数,比如递归,如果有符号说明没有内敛
- debug版本上,inline是不起作用的;inline只有在release版本下才能出现
*/
函数调用的过程:
1 | int sum(int a , int b = 20) |
在这里,第三行参数从右向左压栈,然后调用call指令,call 会把返回地址(main 函数中调用 sum 后的下一条指令)压入栈顶,然后跳转到 sum。要开辟函数栈帧,栈帧包含:返回地址、保存的寄存器、局部变量空间、对齐填充等。调用完后栈帧销毁,返回地址出栈,跳回 main 继续执行。虽然就是简简单单的x+y操作(三行汇编:mov add mov),但是如果循环了100000次呢?每一次都要这样开辟,很耗内存。
inline int sum(int x,int y)
{
return x+y;
}
inline函数建议编译器内联展开函数(这是个建议,而不是强制),比如在第三行,很有可能编译成int ret = a+b而不是调用函数。
函数重载
/*
- c++为什么支持函数重载,c语言不支持函数重载
- C++代码产生函数符号的时候,是由函数名+参数列表组成的;C产生函数符号的时候,只由函数名组成。
可以理解为C++:cmp_int_int / cmp_double_double / cmp_char *_char *,而c就会发生链接错误。
- C++代码产生函数符号的时候,是由函数名+参数列表组成的;C产生函数符号的时候,只由函数名组成。
- 函数重载需要注意什么
- c++和c语言代码之间如何互相调用 (为何无法调用?因为函数符号不同,一个有列表一个没列表,会无法连接,UND)
- C调用C++:无法直接调用,把c++源码扩在extern “C”
- C++调用C:把C函数的声明扩在extern “C”中
*/
什么是重载函数?
- 一组函数,其中函数名相同,参数列表的个数或类型不同,那么这一组函数就称为 函数重载
- 一组函数要称得上重载,一定先是处在同一个作用域中。
- const / volatile,是怎么影响形参类型的。
- 一组函数,函数名相同,参数列表也相同,仅仅是返回值不同,不叫重载 因为函数名参数列表都相同
const的用法
- const与普通变量的区别
- const修饰的变量不能够再作为左值!!!初始化完成后,值不能被修改。
- 不能把常量的地址泄露给一个普通的指针或普通的引用变量 不能int p = const int
- c和c++中const的区别是什么?
const的编译方式不同,c中,const就是当作一个变量来编译生成指令的。
C++中,所有出现const常量名字的地方,都被常量的初始化替换了!!!所以在C++中使用const必须要初始化。比如const int a = 20,接下来的int arr[a],这个a不是a了,而是20。那使用指针有没有修改掉a的值呢?有,已经改掉了。 - c++的const必须初始化,叫常量。如果用变量为const定义的量赋值,就叫常变量(和C一样了),因为初始值不是立即数,是一个变量,这时候printf出来的都会是变量。
1 |
|
const与一级指针/二级(多级)指针的结合
c++语言规范:const修饰离它最近的类型
const右边如果没有指针,是不参与类型的*
const和一级指针的结合
- ①const int p //修饰int,说明p的值不能被改,但是p可以指向其他地方,一和二一样,const附近都是int
- ②int const *p
- ③int *const p //最靠近const的类型是:int *,修饰指针,说明指针指向不能改,但值可以改
总结const指针和指针类型的转换公式 理解:把一个普通指针变为const修饰的指针,告诉编译器:我不会再修改它了,是正确的
- int* <= const int* 是错误的
- const int* <= int* 是正确的
new/malloc delete/free
- delete和free的区别?malloc和free称作C的库函数,new和delete是运算符。
- new不仅可以开辟内存,还可以初始化内存;malloc只负责开辟内存大小的空间,返回的指针是void *,所以要强制类型转换。
c++int *p = (int*)malloc(sizeof(int)*20);
- malloc开辟失败,是通过返回值和nullptr作比较;new开辟失败,通过抛出异常。
- new有几种?
- int *p = new int(20);
- int *p2 = new (nothrow) int; //不抛出异常
- const int *p3 = new const int(40);
- int data = 0;int *p4 = new (&data) int(50); //定位new,在指定的位置赋值
类和对象
OOP语言的四大特征:抽象 封装/隐藏 继承 多态
类:属性->成员变量 行为->成员方法
通过访问限定符体现:public private protected