[C++初阶笔记]P2

常引用(const修饰的引用/权限放大和缩小)

1、权限放大和缩小只针对引用和指针

int main()
{
    //权限的平移
    int a = 10;
    int& b = a;
​
    //只读 > 可读可写 属于权限放大的情况(不能放大)
    const int a1 = 10;
    int& b1 = a1;//编译错误,a1为常量。
​
    //可读可写 > 只读 属于权限的缩小的情况(可以缩小)
    int a2 = 10;
    const int& b2 = a2;
​
    //隐式类型转换(整型提升/算术提升)
    //这里类型转换的过程中会产生一个double的临时变量,它是算术提升a3的结果,最终是将临时变量赋值给b3.
    //类型转化并不会对变量a3产生影响,而是得到变量a3的值,对值进行操作。
    //这里引出隐式类型转换是为了更好得理解下面提到的临时变量
    int a3 = 10;
    double b3 = a3;
​
    //b4引用的不是a4,引用的是中间产生的临时变量,临时变量本身具有常性,相当于被const修饰的一样。
    //这里不能被引用的真正原因是因为权限的放大,权限不能放大的。
    int a4 = 10;
    double& b4 = a4;//编译错误,临时变量具有常性。
​
    //权限的平移
    int a5 = 10;
    const double& b5 = a5;
​
    //虽然精度丢失,但并不会权限放大,所以可行。
    double a6 = 3.14;
    const int& b6 = a6;
​
    //给字面常量取别名
    const int& a7 = 10;
    
    int& a8=10;//编译错误,10为字面常量。
    return 0;
}

类型转换/引用过程 中间会产生临时变量,临时变量本身具有常性。

2、引用作为函数形参时可能导致的权限放大

权限放大和缩小只针对引用和指针

void func1(int n)
{}
void func2(int& n)
{}
void func3(const int& n)//如果使用引用传参,并且函数内并不改变引用,则需要用const修饰。和指针一样。
{}
int main()
{
    int a=10;
    const int b=20;
    
    func1(a);
    func1(66);
    func1(b);//虽然传递的b是有const修饰,func1的形参类型没有const修饰,但传递参数仅仅是拷贝。
             //func1函数中n的改变不会影响b。
    
    func2(a);
    func2(b);//编译错误,因为b是被const修饰的,形参没有const修饰,n的改变会影响b,相当于权限放大。
    func2(66);//编译错误,同理。
    
    func3(a);
    func3(b);
    func3(66);
    return 0;
}

引用和指针的区别

1、引用定义时必须初始化,指针定义时不一定要初始化。

2、引用初始化后不能再引用其他实体,指针可以修改指向任意对应类型的变量。

所以引用不能引用于链表

3、没有空引用,但有空指针。

4、没有多级引用,有多级指针。

5、语法角度来说创建引用不开空间。底层角度,sizeof(引用)为引用类型的大小,sizeof(指针)为4/8字节

底层是按照指针的方式实现的,如下汇编代码对比是一样的。

[C++初阶笔记]P2_第1张图片 

 

6、引用++结果是引用的实体增加1,指针++即指针向后偏移一个类型的大小。

7、访问实体方式不同,指针需要通过*解引用,引用则编译器会自己处理。

8、 指针比引用危险,指针比引用强大。

C++是如何支持函数重载的

1、C/C++代码运行起来需要经过:预编译、编译、汇编、链接。

[C++初阶笔记]P2_第2张图片 

 

预编译:头文件的展开、宏的替换、条件编译、消除注释。

  1. func.h分别在func.c和main.c中展开。

  2. 最后生成func.i和main.i文件

编译:语法检查,生成汇编代码。

func.i和main.i编译后生成汇编代码func.s和main.s(CPU读不懂汇编代码)

汇编:汇编代码转化成二进制机器码。

生成二进制的目标文件func.o和main.o(windows环境下为.obj)

链接:不仅仅是把.o目标文件合并,还要找到声明过的函数/变量的地址

  1. func.o中的函数定义可直接转化为二进制指令

  2. main.o中只包含函数声明,并不知道函数定义的具体过程,链接过程需要中把func.o中函数(定义)的地址传给main.o的函数调用。

  3. 如果链接过程中,main.o中的函数调用时找不到对应的函数(定义)的地址,就会报链接错误。

  4. 每个.o目标文件都会配一个符号表,符号表是函数名/变量名和它们自身地址的映射。

  5. 链接过程中就会拿函数名去符号表中找对应的地址,而C语言的目标文件中的符号表无法区分同名函数。

  6. 生成out.a可执行文件(Windows环境下为.exe)

2、链接的一些细节

[C++初阶笔记]P2_第3张图片 

 

  • .o文件是汇编代码转成的机器码

  • func.o文件中有函数的定义,对应的汇编代码就会建立栈帧的过程。

  • main.o文件中只有函数调用,没有建立栈帧的过程,但是前面函数声明,编译就会允许通过。

  • func.o有函数的定义才会有函数的地址,main.c只有函数声明没有函数地址。

  • 有定义才会有符号表,声明不会进符号表。

  • 链接就是通过符号表拿着函数名把声明过的函数地址找到,找不到就会报链接错误。

  • 全局变量和函数都会记录到符号表

  • 查看符号表:objdump -S [可执行文件名]

3、函数名修饰规则

[C++初阶笔记]P2_第4张图片 

C++为了支持函数重载,编译时会有一个函数名修饰规则,

Linux下符号表中函数名的修饰规则

1、_Z是前缀

2、4表示函数名的长度

3、func表示原本的函数名

4、id表示所有参数类型首字母组合

所以参数个数/类型种类/类型顺序不同,都会引起修饰后的函数名不同。从而也能看出函数重载与函数返回类型无关。

C++程序调用C库

1、用VS生成一个C的静态库。

  • VS默认生成可执行程序(没有main函数就会报错)

  • 将函数声明文件和函数定义文件写好

  • 解决方案资源管理器>右击项目名>属性>常规>配置类型>静态库.lib>应用>生成解决方案

  • 在输出中会显示生成的静态库路径

2、写一个C++调用C静态库的代码

  • 先要包含头文件:#include"生成静态库的头文件的相对路径(或绝对路径)"

  • 也可以直接拷贝到当前路径,只写头文件名字。

  • 代码编译完后链接的时候才会去链接静态库

3、配置链接器设置

  • 解决方案资源管理器>右击项目名>属性>链接器>常规>附加库目录>添加静态库的目录>应用

  • 解决方案资源管理器>右击项目名>属性>链接器>输入>附加依赖项>编辑>添加库的名称>应用

4、extern "C" 告诉C++编译器,链接的时候按C的函数名修饰规则去库中寻找函数

extern "C"
{
    #include"../StackC/Stack.h"
}

C程序调用C++库

1、用VS生成一个C++的静态库。

  • 解决方案资源管理器>右击项目名>属性>常规>配置类型>静态库.lib>应用>生成解决方案

2、写一个C调用C++静态库的代码

  • 先要包含头文件:#include"生成静态库的头文件的相对路径(或绝对路径)"

  • 也可以直接拷贝到当前路径,只写头文件名字。

3、配置链接器设置

  • 解决方案资源管理器>右击项目名>属性>链接器>常规>附加库目录>添加静态库的目录>应用

  • 解决方案资源管理器>右击项目名>属性>链接器>输入>附加依赖项>编辑>添加库的名称>应用

4、按C的函数修饰规则去生成C++库的符号表。

C程序调用C++库需要包含C++库方法的头文件

//C代码包含的头文件
#include"C++静态库头.h文件的路径"

C++库方法的头文件中的函数声明要被extern "C"修饰的,以C的函数修饰规则生成符号表。

//生成C++静态库的.h文件中的代码
extern "C"
{
    //函数声明
}

C程序包含了C++库方法的头文件,那么就会在C程序中展开。

//预处理后的C代码
extern "C"
{
    //函数声明
}

展开后但是C的编译器不认识 extern "C"就会报错,所以要使用条件编译,让其在生成C++静态库时按extern "C"来执行,在C代码中展开后不执行。

//C++库方法的头文件
#ifdef __cplusplus
extern "C"
{
#endif
    //函数声明
    //函数声明
    //函数声明
#ifdef __cplusplus
}
#endif

C++程序就会定义__cplusplus,C程序不会。

你可能感兴趣的:(c++,c语言)