目录
1.什么是声明,什么是定义—参考书籍《C语言深度解剖》
1.1 举个例子:
(A)int i;
(B )extern int i
哪个是定义?哪个是声明?或者都是定义或者都是声明?
1.2 什么是定义?
所谓的定义就是(编译器)创建一个对象,为这个对象分配了一块内存,并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。
注意:
- 这个名字 一旦和这块内存匹配起来,它们就同生共死, 终生不离不弃;并且这块内存的位置也不能被改变。
- 一个变量或对象在一定的区域内(比如函数内、全局等)只能被定义一次; 如果定义多次,编译器会提示用户重复定义了同一个变量或对象。
在头文件是否可以定义全局变量?
1.3 什么是声明
有两重含义:
- 告诉编译器 ,这个名字已经匹配到一块内存上了 ( “ 伊人已嫁 ,吾將何去何从?何以解优,唯有稀粥”),下面的代码用到变量或对象是在别的地方定义的。声明可以出现多次。
- 告诉编译器 ,这个名字已被预定了,别的地方再也不能用它来作为变量名或对象名。比如,如果图书馆自习室的某个座位上被放了一本书,就表明这个座位已经有人预定,别人不允许使用这个座位,其实这个时候占座位的本人并没有坐在该座位上。这种声明最典型的例子就是函数参数的声明,例如:void fin( int i , char c );
编译器是否会报错?在yuan.c使用i是否成功?在yuan.c中仍声明i,是否报错?
通过解释 ,我们可以很清楚地判断上述
1.1举例中 :
(A )是定义 ; ( B )是声明。
extern 用来声明外部符号
记住,定义和声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存(“ 一个抱伊人,一个喝稀粥” )。1.2举例中
头文件不能定义全局变量。
在其他文件包含头文件时,例如一个函数功能实现模块,一个测试模块,首先在预编译期间,会完成头文件替换,而此时两个文件都含有 int i ;在链接期间,就会报链接 Link 错误,因为 i 重定义。
是否可以用其他C语言方法解决?
可以使用static,使其具有内部链接属性,在编译期间,链接属性只在当前的obj文件可见,而引用其头文件在预编译期间会进行头文件替换,替换完成后是两个完全域不相同且不互相影响的具有内部链接属性的量,地址不同,在编译时不会进行符号汇总,所以汇编链接时不产生对应的符号表,不会重定义。
1.3举列中
不会报错
因为声明只是告诉编译器,这个名字匹配到了一块内存,而在预编译链接头文件,声明,会产生对应的符号汇总,在编译期间声明得变量会进入符号表,在链接期间通过符号表汇编指令 call地址 进行链接,因此地址都是一样的。
2.内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
2.1 观察非内联函数的调用汇编说明
在属性页面通过设置,观察汇编代码中是否存在 call 指令,如果出现,说明内联函数并未完成替换
示例代码 1:
int Add(int x, int y) { return x + y; } int main() { int ret = Add(1, 2); return 0; }
汇编指令:
第一步:
第二步:
第三步:
以上便是非内联函数调用说明
2.2 观察内联函数的调用汇编说明
示例代码 2:
inline int Add(int x, int y) { return x + y; } int main() { int ret = Add(1, 2); return 0; }
汇编指令:
通过汇编指令过程可知,内联函数在此操作完成的时替换,直接替换函数操作
2.3 内联函数特性及分析
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规 模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、频繁调用的函数 采用inline修饰,否则编译器会忽略inline特性。
2.4 内联函数的定义和声明
内联函数的定义和声明是不能分开的,必须在同一文件中,才能完成替换。
代码分析:
原理分析:
在预编译期间,编译器会进行以下操作并生成 .i 文件
1.头文件包含 #include——预处理指令,2.#define定义符号的替换——预处理指令
3.注释删除也是在此阶段完成的
此时在yuan.cpp文件中,会有内联函数的声明定义,并进行替换,替换完成后,在编译完成时,并不会将内联函数写入符号表,因此其他文件无法调用内联函数。
而在test.cpp文件中,只有内联函数的声明,(在没有链接之前),生成对应的函数符号表
在链接期间,内联函数定义的文件已经完成此文件内,函数的替换,并没有写入相应的符号表,此时test.cpp文件想要调用函数,因此会报链接错误,无法解析的外部符号。
3. 类、以及类中内联函数 / 及 / 成员方法的声明
3.1 类
可知类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
3.2 类和内联函数的结合使用
由类的定义方式可知,成员函数如果在类中定义,C++在编译会根据其指定的多少判断其是否按照内联函数进行处理。而内联函数的处理中定义和声明是不能分开的,由call指令的是否执行,便可判断成员函数其是否按照内联函数进行处理。
代码分析1:此时类方法的定义和声明分开进行;
汇编分析:
由 call 函数 指令可以看出来,类中函数的分离,便需要通过call 调用函数
代码分析2:此时类方法和声明同时进行,通过反汇编判断是否是内联函数
由此看出,在类方法定义和声明在同一文件中,类方法的调被当作内联函数完成的是替换
3.3 类成员方法的声明和定义
类方法的声明便是在类里进行声明的,类成员方法并不占用空间,只有类实例化时,并调用类成员方法时才会开辟空间
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作 符指明成员属于哪个类域。
class Person { public: void PrintPersonInfo(); private: char _name[20]; char _gender[3]; int _age; }; // 这里需要指定PrintPersonInfo是属于Person这个类域 void Person::PrintPersonInfo() { cout << _name << " " << _gender << " " << _age << endl; }