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字节。
底层是按照指针的方式实现的,如下汇编代码对比是一样的。
6、引用++结果是引用的实体增加1,指针++即指针向后偏移一个类型的大小。
7、访问实体方式不同,指针需要通过*解引用,引用则编译器会自己处理。
8、 指针比引用危险,指针比引用强大。
预编译:头文件的展开、宏的替换、条件编译、消除注释。
func.h分别在func.c和main.c中展开。
最后生成func.i和main.i文件
编译:语法检查,生成汇编代码。
func.i和main.i编译后生成汇编代码func.s和main.s(CPU读不懂汇编代码)
汇编:汇编代码转化成二进制机器码。
生成二进制的目标文件func.o和main.o(windows环境下为.obj)
链接:不仅仅是把.o目标文件合并,还要找到声明过的函数/变量的地址
func.o中的函数定义可直接转化为二进制指令
main.o中只包含函数声明,并不知道函数定义的具体过程,链接过程需要中把func.o中函数(定义)的地址传给main.o的函数调用。
如果链接过程中,main.o中的函数调用时找不到对应的函数(定义)的地址,就会报链接错误。
每个.o目标文件都会配一个符号表,符号表是函数名/变量名和它们自身地址的映射。
链接过程中就会拿函数名去符号表中找对应的地址,而C语言的目标文件中的符号表无法区分同名函数。
生成out.a可执行文件(Windows环境下为.exe)
.o文件是汇编代码转成的机器码
func.o文件中有函数的定义,对应的汇编代码就会建立栈帧的过程。
main.o文件中只有函数调用,没有建立栈帧的过程,但是前面函数声明,编译就会允许通过。
func.o有函数的定义才会有函数的地址,main.c只有函数声明没有函数地址。
有定义才会有符号表,声明不会进符号表。
链接就是通过符号表拿着函数名把声明过的函数地址找到,找不到就会报链接错误。
全局变量和函数都会记录到符号表
查看符号表:objdump -S [可执行文件名]
C++为了支持函数重载,编译时会有一个函数名修饰规则,
Linux下符号表中函数名的修饰规则
1、_Z是前缀
2、4表示函数名的长度
3、func表示原本的函数名
4、id表示所有参数类型首字母组合
所以参数个数/类型种类/类型顺序不同,都会引起修饰后的函数名不同。从而也能看出函数重载与函数返回类型无关。
1、用VS生成一个C的静态库。
VS默认生成可执行程序(没有main函数就会报错)
将函数声明文件和函数定义文件写好
解决方案资源管理器>右击项目名>属性>常规>配置类型>静态库.lib>应用>生成解决方案
在输出中会显示生成的静态库路径
2、写一个C++调用C静态库的代码
先要包含头文件:#include"生成静态库的头文件的相对路径(或绝对路径)"
也可以直接拷贝到当前路径,只写头文件名字。
代码编译完后链接的时候才会去链接静态库
3、配置链接器设置
解决方案资源管理器>右击项目名>属性>链接器>常规>附加库目录>添加静态库的目录>应用
解决方案资源管理器>右击项目名>属性>链接器>输入>附加依赖项>编辑>添加库的名称>应用
4、extern "C" 告诉C++编译器,链接的时候按C的函数名修饰规则去库中寻找函数
extern "C"
{
#include"../StackC/Stack.h"
}
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程序不会。