欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++中部分知识点,引用、this指针、构造函数和析构函数、函数重载

程序员文章站 2022-07-12 15:29:23
...

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; 
} 
函数重载的底层实现:编译器是通过名称修饰分辨不同的函数的,编译器在将C++代码编译成目标文件时,会将函数和变量的名字进行修饰形成不同的符号名。
要想看修饰后的名称,只需在编译器中给出声明,而不给出实现,然后在调用即可。比如

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;