主要讲了const和引用的一些扩展和注意事项,重载函数的和extren "C"结合使用的本质,以及引用的真正的理解方式。
const的引用
扩展使用方法
1.通过const引用的变量是只读变量,当使用const_cast经过强制类型转换后引用将会降级为普通变量,通过改变引用的值能够改变原来只读变量的值,但是仍然无法直接通过改变原来的只读变量的值,这就像换了一个名字可以变了,但是原来名字下的东西仍然不能变。
#include <stdio.h>
int main()
{
const int& a = 1; //const与引用结合让一个变量变为了一个只读变量
int& b = const_cast<int&>(a); //这里通过const_cast来进行强制类型转换,这个时候a就由一个
//只读变量降级为一个普通变量被b引用。
b = 8;
//a = 9; 这条语句经过编译会报错,因为a虽然经过了强制类型转换,但是a仍然是一个只读变量,不够被赋值
printf ("%d\n",b);
printf ("%d\n",a);
}
程序打印结果是两个8,同时注意我们不能够直接对a赋值,因为这个时候
a名字下的那段内存仍然是只读的。
2. const引用的变量是只读变量,这个只读变量仍然能够被其他的const进行引用,但是经过引用之后仍然是只读的,不能够被其他常量赋值。 第二次的引用可以通过const_cast去除const属性,经过强制类型转换以后可以通过改变这个引用的值来改变前两个只读变量的值。也就是说,经过两轮最终换名字成功,这个新名字就可以改变前面连个的内容。
#include <stdio.h>
int main()
{
const int& a = 1;
const int& b = a;
b = 7;
int& d = const_cast<int&>(b);
d = 8;
printf ("%d\n",d);
printf ("%d\n",b);
printf ("%d\n",a);
}
程序最终的打印结果是三个8
同时,无论怎样引用还是去除const属性,这三个量的指向的地址都是一致的,例程如下:
#include <stdio.h>
int main()
{
const int& a = 1;
const int& b = a;
b = 7;
int& d = const_cast<int&>(b);
d = 8;
printf ("%p\n",&d);
printf ("%p\n",&b);
printf ("%p\n",&a);
}
3.volatile关键字的作用是使变量每次都从内存中重新取值,那么在C++中,
volatile和const进行搭配修饰一个变量的时候,const变量将不再进入符号表,而是变成一个只读变量,通过指针可以对只读变量的值进行更改。
#include <stdio.h>
int main()
{
volatile const int a = 1;
int *p = NULL;
p = const_cast<int*>(&a);
*p = 2;
printf ("%d", a);
printf ("%d", *p);
}
程序分析:上面的程序定义了一个volatile和const修饰的变量a,通过强制类型转换将a转换成一个变量的地址,也就是指针p,这里通过改变指针p所指向的内存中的值,程序打印结果发现a的值也跟着改变了,也就是说当volatile和const进行搭配的时候,修饰的变量是一个只读变量,不再进入符号表。
4.当定义的const常量的初始值是经过volatile和const修饰的常量赋值的,那么这次初始的const常量也不会被放入符号表,这是C++编译器的一种规则。
例程:
#include <stdio.h>
int main()
{
volatile const int a = 1;
int *p = NULL;
p = const_cast<int*>(&a);
*p = 2;
printf ("%d", a);
printf ("%d", *p);
const int b = a;
p = const_cast<int*>(&b);
*p = 9;
printf ("%d", b);
printf ("%d",*p);
printf ("%d",a);
}
在上面的程序中b的值是可以被改变的,也就是说这个时候b是一个只读变量,不会进入符号表。
6.不同类型的变量初始化const引用的时候将变为一个只读变量。
#include <stdio.h>
int main()
{
int a = 'a';
int& b = a;
const char& c = b;
b = 'b';
printf ("%c", a);
printf ("%c", b);
printf ("%c", c);
}
打印结果是bba,这里的引用c的初始化被一个int类型的引用b来初始化,这个时候产生的c仍然是一个只读变量。所以打印结果是bba
7.符号表
符号表是编译器在编译的过程中产生的关于程序中语法符号的数据结构(如常量表、变量名表、数组名表、函数名表等),符号表是编译器自己使用的内部数据结构,和我们所写的程序本身并没有关系,符号表不会进入最终产生的可执行程序中。
const和引用的总结
1. 只有字面量初始化的const常量才会进入符号表,也就是变为真正意义上的常量。①对const常量进行引用会导致编译器为其分配空间
②虽然const常量被分配了空间,但是这个空间中的值并不会被真正的使用
③使用其他变量初始化的const常量仍然是只读变量
2.被volatile修饰的const常量不会进入符号表
被volatile修饰的常量退化为只读变量,每次访问都要从内存中重新取值。
3const引用类型与初始化变量的分为两个方面
①如果这两个类型相同,初始化变量将变为只读变量
②如果这两个类型不同,将生成一个新的只读变量,其初始值与初始化变量相同
引用和指针
引用和指针的区别
指针是一个变量,其值是一个内存地址,通过指针可以访问指针对应的内存地址中的值。引用是一个变量的新名字,所有对引用的操作都会传递到引用的变量上。指针可以被const修饰成为常量或只读变量。const引用使其引用的变量具有只读属性。指针就是变量,不需要初始化,也可以指向不同的地址。引用必须在定义的时候就初始化,之后无法引用其他变量。因为引用在C++编译器的内部实现机制是指针产量,所以说必须进行初始化。那么你说引用是不是对指针常量的一个封装呢?哈哈~胡拆的哈~
如何理解引用的本质就是指针常量
首先,引用的本质就是指针常量,这是C++的一种设计模式,这和为什么C语言中有指针都是一个类型的问题,因为语言的设计嘛。
从C++语言角度看,引用和指针常量没有任何关系,引用时变量的新名字,操作引用就是操作对应的变量。
从C++编译器的角度看,为了支持新概念“引用”必须有一个有效的解决方案,在编译器内部使用指针常量来实现引用,因此引用在定义时必须初始化。
一个学习过程中的例程:
#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;
}
重载和C方式编译
例程:
#include <stdio.h>
void func(int a, int b)
{
printf("1\n");
}
void func(int a, char b)
{
printf("2\n");
}
void func(char a, int b)
{
printf("3\n");
}
void func(char a, char b)
{
printf("4\n");
}
int main()
{
int a = 3;
char b = 'a';
func(a, a);
func(a, b);
func(b, a);
func(b, b);
return 0;
}
上面的程序可以进行顺利编译,这在前面的博文也说过,如果多个函数进行重载,且函数名相同,那么C++编译器会根据函数的参数类型进行匹配来调用函数。
例程:
#include <stdio.h>
void func(int a, int b)
{
printf("1\n");
}
void func(int a, char b)
{
printf("2\n");
}
void func(char a, int b)
{
printf("3\n");
}
void func(char a, char b)
{
printf("4\n");
}
int main()
{
int a = 3;
char b = 'a';
func(a, a);
func(a, b);
func(b, a);
func(b, b);
func(3, a);
func(3 'a');
func('a', 3);
func('a', '3');
return 0;
}
上面的程序在VS2008的编译环境将能够编译通过,但是如果换作其他编译器并能保证(手上除了VS没有其他的了,所以只能听老师的了)。
C++对字面常量的处理方式
当使用字面常量对对变量进行初始化或者赋值的时候,编译器会根据变量的类型和字面量进行比较,如果左面的变量能够容下右面的值,那么将不会产生溢出如果左面的值不能够容下右面的值,将会产生溢出和截断。
函数重载的深度规则
①精确匹配实参
②通过默认类型转换匹配实参
③通过默认参数匹配实参
C方式编译
extern ”C“ 通知C++编译器将其中的代码进行C方式的编译,C方式的主要指按照C语言的规则对函数名进行编译,函数名通过编译可能与源代码中的名字有所不同,C++编译器为了支持重载,函数名经过编译后会加上参数信息,因而编译后的函数名与源码中的完全不同。C编译器不会在编译后的函数名中加上参数信息。extern “C”中的重载函数经过C方式编译后将得到相同的函数名,因此extern “C”中不允许重载函数,但extern “C”中的函数可以与extern “C”之外的函数进行重载。
例程如下:
#include <stdio.h>
extern "C"
{
void func(int n)
{
const int a = 1;
int& b = const_cast<int&>(a);
b = 5;
printf("i = %d\n", a);
printf("ri = %d\n", b);
}
}
void func(const char* s)
{
printf("%s\n", s);
}
int func(int a, int b)
{
return a + b;
}
int main()
{
func(1);
func("abc");
func(1, 2);
return 0;
}
#include <stdio.h>
extern "C"
{
void func(int n)
{
const int a = 1;
int& b = const_cast<int&>(a);
b = 5;
printf("i = %d\n", a);
printf("ri = %d\n", b);
}
int func(int y, int z)
{
return y;
}
}
void func(const char* s)
{
printf("%s\n", s);
}
int func(int a, int b)
{
return a + b;
}
int main()
{
func(1);
func("abc");
func(1, 2);
return 0;
}
上面的程序将会编译报错,因为在extern ”C“中我们又对func函数进行了重载,这个时候通过C方式编译出来的程序将会有两个没有参数信息的函数func,所以报错。