第6课 - 专题一经典问题解析
一. const和引用的疑惑
Source Example 1.1:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
/* 定义一个真正的常量,字面量进行初始化 */
const int x = 1;
/* 对常量取地址和对常量去引用都会导致编译器为其分配空间 */
int &rx = const_cast<int&>(x);
rx = 5;
printf ("x = %d\n", x);
printf ("rx = %d\n", rx);
printf ("&x = %p\n", &x);
printf ("&rx = %p\n", &rx);
/* volatile关键字表示编译器不进行优化,每次都到内存去取值 */
volatile const int y = 2;
int *p = NULL;
p = const_cast<int *>(&y);
*p = 6;
/* 输出结果为都为6,表示编译器到内存去取值,y没有进入符号表,y只是一个只读变量 */
printf ("y = %d\n", y);
printf ("*p = %d\n", *p);
/* 变量进行初始化const常量 */
const int z = y;
p = const_cast<int *>(&z);
*p = 7;
/* 输出结果为都为7,表示编译器到内存去取值,y没有进入符号表,z只是一个只读变量了 */
printf ("z = %d\n", z);
printf ("*p = %d\n", *p);
char c = 'c';
char &rc = c;
/* 不会使得c拥有const属性 */
/* 不同类型的变量初始化引用之后会导致const引用产生一个新的只读变量 */
const int& trc = c;
rc = 'a';
/* 输出结果为a,a,c */
printf ("c = %c\n", c);
printf ("rc = %c\n", rc);
printf ("trc = %c\n", trc);
return 0;
}
总结:
1. 什么是符号表?存储在程序中的那个地方?
a.符号表示编译器在编译过程中产生的关于源程序中语法符号数据结构
如常量表,变量名表,数组名表,函数名表等等
b.符号表示编译器自用的内部数据结构
c.符号表不会进入最终产生的可执行程序中
2. 只有用字面常量初始化的const常量才会进入符号表
例如 const int x = 1;
a.对const常量进行引用会导致编译器为其分配空间
b.虽然const常量被分配了空间,但是这个空间中的值不会被使用
c.通过其他变量初始化的const常量仍然是只读变量
3. 被volatile修饰的const常量不会进入符号表
退化为只读变量,每次访问都从内存中取值
4. const引用的类型与初始化变量的类型
a.相同:使初始化变量成为只读变量
b.不同:生成一个新的只读变量,其初始化值与初始化变量相同
如果在编译期间不能直接确定初始值得const量,都被作为只读变量处理!
二.引用与指针的疑惑
2.1 引用与指针的区别
2.1.1 指针是一个变量,其值为一个内存地址,通过指针可以访问对应内存地址中的值
2.1.2 引用只是一个变量的名字,所有对引用的操作(赋值,取地址等)都回传递到其引用的变量上
2.1.3 指针可以被cons修饰成为常量或者只读变量
2.1.4 const引用使其引用的变量具有只读属性
2.1.5 指针就是变量,不需要初始化,也可以指向不同的地址
2.1.6 指针天生就必须在定义时初始化,之后无法再引用其他变量
2.2 如何理解引用的本质就是指针常量
2.2.1 从C++语言角度看
a.引用与指针常量没有关系
b.引用时变量的新名字,操作引用就是操作对应的变量
2.2.2 从C++编译器角度看
a.为了支持新概念"引用",必须要有一个有效的解决方案
b.在编译器内部,使用指针常量来实现"引用"
c.因此"引用"在定义的时候必须初始化
三.重载的疑惑
Source Example3 :
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
void func(int a, int b)
{
printf ("int a, int b\n");
}
void func(int a, char b)
{
printf ("int a, char b\n");
}
void func(char a, int b)
{
printf ("char a, int b\n");
}
void func(char a, char b)
{
printf ("char a, char b\n");
}
int main(int argc, char** argv) {
func(1,2);
func(1,'2');
func('1',2);
func('1','2');
return 0;
}
结果的输出与编译器有关,因为编译器有可能将1或者2与char型进行匹配从而报错。(由于1,2,都小于255没有溢出)
具体原因如下
3.1 C++编译器对字面量的处理方式
3.1.1 整数型字面量默认类型为Int,占用4个字节
3.1.2 浮点型字面量的默认值为double,占用8个字节
3.1.3 字符型字面量的默认值为char,占用1个字节
3.1.4 字符串型字面量的默认值为const char *,占用4个字节
3.2 当使用字面量对变量进行初始化或赋值时
3.2.1 无溢出产生,编译器进行默认类型转换
3.2.2 产生溢出,编译器会做截断操作,并产生警告
四.C方式编译的疑惑
Source Example 4:
#include <iostream>
extern "C" {
void func (int x)
{
const int i = 1;
int &ri = const_cast<int &>(i);
ri = 5;
/* 输出1,5 */
printf ("i = %d\n", i);
printf ("ri = %d\n", ri);
}
}
void func (int a, int b)
{
printf ("aaa");
}
int main(int argc, char** argv) {
func (1);
return 0;
}
当使用extern关键字编译func函数时,里面使用了const_cast等C++特有的关键字,编译成功了,同时输出了1,5,表示i确实是一个真是的常量,不是可读变量。
深入汇编代码研究,发现用C++的编译方式和C编译方式只有func的函数名不同,其余都相同。
具体原因如下
4.1 深入理解extern "C"
4.1.1 extern "C"告诉C++编译器将其中的代码进行C方式编译
a.C方式编译主要按照C语言的规则对函数名 进行编译
函数名经过编译后可能与源码中的名字有所不同
C++编译器为了支持重载,函数名经过编译后加上参数信息,因而编译后的函数名与源码中完全不同
C编译器不会在编译后的函数名中加上参数信息
extern "C"中的重载函数经过C方式编译后得到相同的函数名,因此extern "C"不允许重载函数,但extern "C"中的函数可以与extern "C"之外的函数进行重载