C++中部分知识点,引用、this指针、构造函数和析构函数、函数重载
1.引用类型
引用类型是C++新增(相对C语言)的一种类型,引用是已定义的变量(对象)的别名。引用的定义方法为
类型名 & 引用变量名 = 被引用变量名;
int i = 0;
int b = 5;
int &ri = i;
ri = b; //这实际上将i和ri的值改为了5,而不是使ri引用b
引用的本质实际上是一个常量指针,int * const rp = &i; ri相当于rp,只是在使用时不必像指针一样需解引用。
引用的特性如下:
- 引用必须被初始化,因为引用是一个常量指针。
- 一个变量可以被多个引用变量引用。
- 一个引用一旦引用一个变量则不能引用其它变量,因为引用是一个常量指针。
- 对引用的改变就是对被引用的变量本身的改变。
const引用:
当引用的类型和被引用的变量的类型不一致时,编译器将报错。例如
double d;
int &i = d;
//const int &i = d;
但加上const后编译器不会报错,此时编译器自动生成一个和引用相同类型的临时变量,引用的是这个临时变量,而不是之前不同类型的变量。
引用的应用场景:
- 将引用作函数形参
- 将引用用作函数返回值类型
当引用作函数形参时,应尽可能加上const,原因如下:
- 使用const可以避免无意中修改外部实参;
- 使用const能够使函数处理const和非const实参,否则只能接受非const数据;
- 使用const引用使函数能够正确生成并使用临时变量。
还有需要注意的是,当调用引用作函数形参的函数时,传递给引用的是对象的地址,虽然调用时并没有加上取地址符号,这是因为编译器自动完成的。看下面的例子,在main函数内并不会崩溃,而是在Swap函数内崩溃,虽然*p看起来是解引用,但编译器只传递*p的地址,也就是把NULL传给了Swap函数,而引用底层是指针,所以在执行语句left=right;时会访问地址为0处的内容,从而造成崩溃。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a = 10;
int* p = NULL;
Swap(a, *pa);
return 0;
}
当引用用作返回值类型是,应注意不要返回临时变量的引用,因为在函数调用完毕后它将不复存在。同样应避免返回指向临时变量的指针。
数组的引用
int a[10];
int (&ra)[10] = a;
引用和指针的区别:
- 引用在定义时必须初始化,指针没有要求
- 一旦一个引用被初始化为指向一个对象,就不能再指向其他对象,而
指针可以在任何时候指向任何一个同类型对象 - 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地
址*空间所占字节个数 - 引用自加改变变量的内容,指针自加改变了指针指向
- 有多级指针,但是没有多级引用
- 指针需要手动寻址,引用通过编译器实现寻址
- 引用比指针使用起来相对更安全
2.this指针
每个类非静态成员函数都含有一个指向被调用对象的指针 这个指针被称为 this。
当两个不同的类对象调用同一个成员函数print时,假若该成员函数输出类对象的值,那么该成员函数如何区分两个不同的对象呢?
此时this指针就起作用了,当对象a调用print时,对象a的地址便传递给this指针,此时输出a对象的内容;当对象b调用print时,对象b的地址传递给this指针,此时输出对象b的内容。
当在成员函数内访问数据成员时编译器实际上是通过this指针访问的,当然在成员函数内可以显示的使用this指针。
看一个例子。这个程序是不会崩溃的,pt是直接传递给this指针,而不是通过指针访问FunTest函数,虽然看起来像通过指针访问,类似于上面引用传参的例子。
class Test
{
public:
void FunTest()
{
cout<<"FunTest():"<<this<<endl;
}
};
int main()
{
Test* pt = NULL;
pt->FunTest();
return 0;
}
3.构造与析构函数
构造函数是类中的一个特殊的成员函数,专门用于构造新对象、将值赋给它们的数据成员。其名字和类名相同,没有返回值,在创建对象时由编译器自动调用,在对象的生命周期内只调用一次。
构造函数可以重载,也可带有缺省参数。
如果没有显示定义,则编译器自动提供一个默认构造函数。
无参构造函数和全缺省的构造函数都属于默认构造函数。
初始化数据成员既可用参数初始化表,也可在构造函数体内进行初始化。
类中数据成员的初始化顺序与数据成员的声明顺序相同,而与初始化列表中的顺序无关。
只接受一个参数的构造函数定义从参数类型到类类型的转换,如果想禁用自动类型转换,可以在声明构造函数时加上关键字explicit。如Test类, Test(int a);则下面的语句合法。
Test a;
a = 4; //自动类型转换,从int类型到Test类型转换
析构函数
对象过期时,程序将自动调用一个特殊的成员函数——析构函数。
析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理的工作,使这部分内存可以被程序分配给新对象使用。
析构函数为的名称:在类型名前加~ 。如Test类的析构函数~Test(){}
析构函数不返回值,没有函数参数。由于没有参数,因此不能重载。一个类可以有多个构造函数,但只有一个析构函数。
如果没有显示的定义析构函数,则编译器默认声明一个析构函数。
4.函数重载
定义:在同一作用域内,函数名相同,而函数参数的类型或数目或排列顺序不同则构成函数重载。
仅仅是返回值类型不同不构成函数重载。
int test()
{}
void test()
{}
下面的例子不构成重载
void FunTest(int a = 10)
{
cout<<"void FunTest(int)"<<endl;
}
void FunTest(int a)
{
cout<<"void FunTest(int)"<<endl;
}
下面的例子虽然构成函数重载,但不传参调用时将产生二义性,编译器报错。
void FunTest(int a = 10)
{
cout<<"void FunTest(int)"<<endl;
}
void FunTest()
{
cout<<"void FunTest(int)"<<endl;
}
void fun(int a);
void fun();
1.obj : error LNK2019: 无法解析的外部符号 "void __cdecl fun(int)" ([email protected]@[email protected]),该符号在函数 _main 中被引用
1.obj : error LNK2019: 无法解析的外部符号 "void __cdecl fun(void)" ([email protected]@YAXXZ),该符号在函数 _main 中被引用
[email protected]@[email protected]就是进行名称修饰后的函数名,当然在不同的编译器中名称修饰方法不同,因此,相同的函数在经过名称修饰后名字可能不同。
在C++代码中如果想让函数或变量按C语言的名称修饰方法进行修饰,则可以加上extern “C”;
extern "C" int fun();
extern "C" int a;
上一篇: C++ this指针与const成员函数
下一篇: 面向对象知识点4