一、const和引用的疑惑
示例代码1:
#include <stdio.h>
int main()
{
const int x = 1;
const int& rx = x;
int& nrx = const_cast<int&>(rx);
nrx = 5;
printf("x = %d\n", x);
printf("rx = %d\n", rx);
printf("nrx = %d\n", nrx);
printf("x = %p\n", &x);
printf("rx = %p\n", &rx);
printf("nrx = %p\n", &nrx);
printf("\n");
/*****************************************************/
volatile const int y = 2;
int* p = NULL;
p = const_cast<int*>(&y);
*p = 6;
printf("y = %d\n", y);
printf("*p = %d\n", *p);
printf("\n");
/*****************************************************/
const int z = y;
p = const_cast<int*>(&z);
*p = 7;
printf("z = %d\n", z);
printf("*p = %d\n", *p);
const int yy = 3;
const int zz = yy;
p = const_cast<int*>(&z);
*p = 7;
printf("zz = %d\n", zz);
printf("*p = %d\n", *p);
printf("\n");
/*****************************************************/
char c = 'c';
char& rc = c;
const int& trc = c;
rc = 'a';
printf("c = %c\n", c);
printf("rc = %c\n", rc);
printf("trc = %c\n", trc);
return 0;
}
编译运行结果:
x = 1
rx = 5
nrx = 5
&x = 0x7ffe97b5e228
&rx = 0x7ffe97b5e228
&nrx = 0x7ffe97b5e228
y = 6
*p = 6
z = 7
*p = 7
zz = 3
*p = 7
c = a
rc = a
trc = c
将代码分成四段来分析。
第一段:对const常量进行引用会导致编译器为其分配空间,并且引用名所对应的是分配出来的空间,而不是符号表中的常量;虽然const常量被分配了空间,但是在使用原来的常量名时这个空间中的值不会被使用;
第二段:被volatile修饰的const常量不会进入符号表,它退化为只读变量,每次访问都从内存中取值;
第三段:在编译过程中,y的值需要访问内存后才能知道,而yy的值是在符号表中的,在编译期间就能知道它的值。所以在编译期间不能直接确定初始值的const量,都被作为只读变量处理;能直接确定的进入符号表;
第四段:const引用的类型与初始化变量的类型相同时,使初始化变量成为只读变量;不同时,生成一个新的只读变量,其初始值与初始化变量相同。
二、引用与指针的疑惑
示例代码:
#include <stdio.h>
struct SV
{
int x;
int y;
int z;
};
struct SR
{
int& x;
int& y;
int& z;
};
int main()
{
SV sv = {1, 2, 3};
SR sr = {sv.x, sv.y, sv.z};
printf("&sv = %p\n", &sv);
printf("&sv.x = %p\n", &sv.x);
printf("&sv.y = %p\n", &sv.y);
printf("&sv.z = %p\n", &sv.z);
printf("&sr = %p\n", &sr);
printf("&sr.x = %p\n", &sr.x);
printf("&sr.y = %p\n", &sr.y);
printf("&sr.z = %p\n", &sr.z);
SV& rsv = sv;
rsv.x = 4;
rsv.y = 5;
rsv.z = 6;
printf("sv.x = %d\n", sv.x);
printf("sv.y = %d\n", sv.y);
printf("sv.z = %d\n", sv.z);
return 0;
}
编译运行结果:
&sv = 0x7fff21bcfee0
&sv.x = 0x7fff21bcfee0
&sv.y = 0x7fff21bcfee4
&sv.z = 0x7fff21bcfee8
&sr = 0x7fff21bcfef0
&sr.x = 0x7fff21bcfee0
&sr.y = 0x7fff21bcfee4
&sr.z = 0x7fff21bcfee8
sv.x = 4
sv.y = 5
sv.z = 6
1、指针与引用的区别:
指针是一个变量,其值为一个内存地址,通过指针可以访问对应内存地址中的值,而引用只是一个变量的新名字,所有对引用的操作(赋值,取地址等)都会传递到其引用的变量上;指针可以被const修饰成为常量或者只读变量,而const引用使其引用的变量具有只读属性;指针就是变量,不需要初始化,也可以指向不同的地址,而引用天生就必须在定义时初始化,之后无法在引用其它变量。
2、如何理解“引用的本质就是指针常量”?
从使用C++语言的角度来看:引用与指针常量没有任何的关系,引用是变量的新名字,操作引用就是操作对应的变量;
从C++编译器的角度来看:为了支持新概念“引用”必须要一个有效的解决方案,在编译器内部,使用指针常量来实现“引用”,因此“引用”在定义时必须初始化。
当进行C++编程时,直接站在使用的角度看待引用,与指针毫无关系!当对C++程序中的一些涉及引用的bug或者“奇怪行为”进行分析时,可以考虑站在C++编译器的角度看待引用!
三、重载的疑惑
示例代码1:
#include <stdio.h>
int main()
{
printf("sizeof(\'1\') = %d\n", sizeof('1'));
printf("sizeof(2) = %d\n", sizeof(2));
printf("sizeof(3.0) = %d\n", sizeof(3.0));
char c = '1';
short s = '1';
int i = '1';
long l = '1';
long long ll = '1';
c = 2;
c = 1000;
s = 2;
i = 2;
l = 2;
ll = 2;
float f = 0;
double d = 0;
f = 3.0;
d = 3.0;
return 0;
}
编译:
example6-3.cpp:16:7: warning: overflow in implicit constant conversion [-Woverflow]
c = 1000;
运行:
sizeof('1') = 1
sizeof(2) = 4
sizeof(3.0) = 8
C++编译器对字面量的处理方式:
1、整数型字面量的默认类型为int,占用4个字节
2、浮点型字面量的默认类型为double,占用8个字节
3、字符型字面量的默认类型为char,占用1个字节
4、字符串型字面量的默认类型为const char*,占用4个字节
当使用字面量对变量进行初始化或赋值时,
无溢出产生:编译器对字面量进行默认类型转换;
产生溢出:编译器会做截断操作,并产生警告;
示例代码2:
#include <stdio.h>
void func(int a, int b)
{
printf("void func(int a, int b)\n");
}
void func(int a, char b)
{
printf("void func(int a, char b)\n");
}
void func(char a, int b)
{
printf("void func(char a, int b)\n");
}
void func(char a, char b)
{
printf("void func(char a, char b)\n");
}
int main()
{
int a = 1;
char b = '2';
func(a, a);
func(a, b);
func(b, a);
func(b, b);
func(1, 2);
func(1 '2');
func('1', 2);
func('1', '2');
return 0;
}
编译运行:
void func(int a,int b)
void func(int a,char b)
void func(char a,int b)
void func(char a,char b)
void func(int a,int b)
void func(int a,char b)
void func(char a,int b)
void func(char a,char b)
深入理解重载规则:精确匹配实参、通过默认类型转换匹配实参和通过默认参数匹配实参。三条规则会同时对已存在的重载函数进行挑选,当实参为变量并能够精确匹配形参时,不再进行默认类型转换的尝试;当实参为字面量时,编译器会同时进行精确匹配和默认类型转换的尝试。
四、C方式编译的疑惑
示例代码:
extern "C"
{
void func(int x)
{
const int i = 1;
int& ri = const_cast<int&>(i);
ri = 5;
printf("i = %d\n", i);
printf("ri = %d\n", ri);
}
}
void func(const char* s)
{
printf("%s\n", s);
}
void func(int a, int b)
{
printf("%d\n",a+b);
}
int main()
{
func(1);
func("chuckie_chen");
func(1, 2);
return 0;
}
编译成功,运行:
i = 1
ri = 5
chuckie_chen
3
深入理解extern “C”:
extern “C”告诉编C++译器将其中的代码进行C方式的编译,C方式的编译主要指按照C语言的规则对函数名进行编译,函数名经过编译后可能与源码中的名字有所不同,C++编译器为了支持重载,函数名经过编译后会加上参数信息,因而编译后的函数名与源码中完全不同,而C编译器不会在编译后的函数名中加上参数信息,extern “C”中的重载函数经过C方式编译后将得到相同的函数名。因此extern “C”中不允许重载函数,但extern “C”中的函数可以与extern “C”之外的函数进行重载。在C++编译器中,C方式和C++方式主要的不同就是函数名编译方式的不同。