一、函数重载
函数重载是什么呢?
概念:函数名相同,参数不同(类型、个数、顺序)
为什么C++支持函数重载,而C语言显然不支持呢?
那就得提到另一个概念:函数名修饰规则
函数名修饰规则
补充:编译链接过程
一个程序运行起来,首先要有几个基本的过程:预处理、编译、汇编、链接。我们今天来简单回顾一下这几个过程发生的事情。
上一个小小的图:
预处理:
- 将所有的#define 删除,并展开所有的宏定义。
- 处理所有的条件编译指令:#if 、#ifdef、 #elif 、#else 、#endif
- 处理#include预编译指令,将被包含的文件插入到该指令的位置,这个过程是递归的
- 删除所有注释
- 添加行号与文件标识,以便于调试用的行号信息以及编译错误或警告时能够显式行号。
- 保留所有#pragma编译器指令,因为编译器需要他们。
编译
把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成汇编代码。这个过程时程序构建的核心部分。
汇编
汇编代码->机器指令
链接(静态链接)
多个目标文件、库变为最终的可执行文件
合并段表、符号表的重定位
—————————————————
接下来回到正题
我们来看看采用C语言编译的结果
采用C++编译的结果
这样一来就一目了然了,C++编译后对于函数命名有了新的规则,这样一来就算函数名一样,但只要参数列表不同,那再编译后是不同的函数,不同的地址。这样一来,C++就完美的支持函数重载。
二、引用
引用就是给已经存在的变量取一个别名,不会开辟新的空间。(注意不可以常引用)
概念没有什么,这里提一下引用的几点特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
注意:取地址和引用符号一样,那如何区分呢?
在对象的前面就是取地址,在类型的后面就是引用。
使用场景
1.做参数
a. 输出型参数
在我们做oj题时,就会出现这种
int* func(int n,int * returnsize);
int* func(int n,int & returnsize);
void swap(int& p,int&q);
我们就直接返回returnsize就好了,不用解引用再去赋值返回。
b.提高效率,减少拷贝
Data d;
void* f(Data dd);
void* f(Data* pdd);
void* f(Data& rdd);
这是显而易见的,不需要去拷贝,只是一个别名。
c.在无头、单向、不循环链表中
我们pushback时候,一般用二级指针或者引用解决传结点点的问题,显然引用具有很大的优势。
2.做返回值
a.修改返回值
//这是正常的用法,但是并不是我们的期望
class string
{
char operator[](size_t)
{
return _str[i];//它是一个右值,不能改变
}
private:
char* _str;
}
string s("hello world");
s[2] ->> s.operator[](2)
我们期望不仅可以访问,也可以修改,那样就会用到引用。
char& operator[](size_t)
{
return _str[i];//左值,返回别名。
}
s[2] += 1;//这条代码是可以通过的,
//对于上面的返回char则不行,
//返回的是它的拷贝,并不是原字符串。
//返回值为引用的话,可以修改它的值了
b. 提高效率,减少拷贝
注意:并不是所有的都可以用引用返回,出了当前函数的作用域,返回对象还在,就可以用引用返回,否则就只能用传值返回。
class Date
{
Date operator+(int day)
{
return tmp;//临时变量不能引用,出函数时候栈帧销毁,返回拷贝。
}
Date& operator+(int day)
{
return *this;//不需要拷贝,减少了拷贝.
}
}
传值返回:
1.会生成一个临时变量的拷贝,如果对象比较小(4、8byte)这时候一般用寄存器存。
2.如果对象比较大,会在调用它的函数栈帧中开辟一个临时变量存这个拷贝的值。
传引用返回:
返回this指针,指向调用它的函数中的对象,所以销毁了,也无所谓,所以不需要拷贝,提高了效率。
三、默认成员函数
构造函数
特性:
1.函数名与类名相同
2.无返回值
3.对象实例化时编译器自动调用对应的构造函数(完成对象的初始化)
4.构造函数可以重载(有多种初始化的方式)
class Date
{
1、无参构造函数
Date()
{
}
2、带参构造函数
Date(int year,int month,int day)
{
_year = year;
}
private:
int _year;
}
---------------------------------------------------
调用无参构造函数
Date d1;//这里不要有括号,不然就成了函数声明。
调用带参构造函数
Date d2(2015);
默认构造函数:
1、如果类中没有显式定义,则C++自动生成一个无参的默认构造函数,用户一旦显式定义则编译器不再自动生成。
2、无参构造函数和全缺省构造函数都成为默认构造函数。默认构造函数只能有一个。
我们没有写编译器自动生成的构造函数,我们写的无参构造函数和全缺省构造函数。都是默认成员函数。
注意:
编译器生成的默认构造函数会对自定义成员调用它的默认成员函数初始化。而不会管内置类型。
这里是C++早期的缺陷,而C++11中有了弥补
可以再成员变量后赋值,这里不是初始化,而是给缺省值。
private:
int a = 1;//缺省值
构造函数初始化列表可以认为是成员变量定义初始化的地方
初始化列表,你写或者不写都会走一遍。
有三种成员变量必须在初始化列表中初始化
1、const
2、引用
3、没有默认构造函数的成员对象。
析构函数
在对象销毁时会自动调用并完成资源清理的工作
特性:
1、析构函数在类名前加~
2、无参数无返回值
3、一个类只有一个析构函数,若未定义,则编译器自动生成默认的析构函数。
4、对象生命周期结束时,C++编译系统自动调用析构函数。
~SeqList()
{
if(_pDate)
{
free(_pDate);//释放堆上的空间
_pDate = NULL ; //指针置空
_capacity = 0;
_size = 0;
}
}
编译器自动生成的默认析构函数,会对自定义成员调用它自己的析构函数。
注意:
析构函数有两个空间要销毁:
1、资源清理。比如指针指向的堆空间的释放等,叫做析构函数清理。
2、销毁对象本身,它是函数的结束,战阵销毁,它就销毁。
拷贝构造函数
只有单个形参,该形参是对本类对象的引用且用const修饰,在用已存在类类型对象创建新对象时由编译器自动调用。
特征:
1、拷贝构造时构造函数的重载
2、拷贝构造的参数只有一个且必须使用引用传参,传值会引发无穷递归调用,每次实参传进来都需要调用拷贝构造函数,如果传引用,则直接进入函数。
3、若未定义,系统生成默认拷贝构造函数,它对基本内置类型完成值拷贝或者叫做浅拷贝,它是按内存存储的字节序完成拷贝的。它对自定义类型成员会去调用它自己的拷贝构造。
像string这样的类,浅拷贝是不行的,默认生成的拷贝构造是不能用的,我们需要自己写,那为什么不能用呢?
这样会发生多次析构同一个空间两次的情况,万一第一次析构后,那个空间被别人用了,再次析构的时候会发生错误释放,这样编译器是不允许的。
我们来自己实现拷贝构造,string深拷贝
主要原理就是,给一个新的空间,并把需要拷贝的值赋过去。
s2(s1)
string(const string& s)
:_str(new char[strlen(s._str)+1])//加1是加上'\0'
{
strcpy(_str,s._str);
}
赋值运算符的重载
运算符重载
函数原型:返回值类型 operator操作符(参数列表)
注意:
1、不能通过链接其他符号来创建新的操作符:operator@
2、重载操作符必须有一个类类型或者枚举类型的操作数
3、用于内置类型的操作符,含义不能改变
4、形参看起来比操作数少一,其实有一个隐藏的this指针,限定为第一个
5、这几个运算符不能重载:
- .*
- ::
- sizeof
- ?:
- .
赋值运算符只要有四点
1、参数类型
2、返回值
3、检测是否给自己赋值
4、返回*this
5、一个类如果没有显式定义赋值运算符重载,编译器会自动生成一个,完成对象按字节的值拷贝
默认生成的operator=,基本类型完成浅拷贝,自定义类型回去调用他们各自的operator=
string& operator=(const string& s)
{
if(this != &s)
{
delete[] _str;
_str = new char[strlen(s._str)+1];
strcpu(_str,s._str);
}
return *this;
}
四、匿名对象
A a1;正常对象,生命周期为整个作用域
A (); 匿名对象,生命周期仅为这一行,
这一行过了就会调用其析构函数。
匿名对象有什么用呢?只为调用其成员函数。
举个例子:
A( ). print( ) ;
在之前我们了解到,对象作为函数实参传递的时候,形参如果没有用引用,则会产生一个临时对象,传给形参。
像是这样:
void f1 (A aa)
{}
int main()
{
A a1;
f1(a1);
}
这在函数调用时,会产生临时对象的拷贝构造;
再来看看这是什么意思?
f1(A());
当我们把匿名对象传过去的时候,我们得知并没有进行拷贝构造,而且也没有传引用,这是为什么呢?
首先我告诉大家,这是编译器的优化行为。
这优化有什么特点呢?
在同一个表达式中,产生一个临时对象,再用临时对象去拷贝一个对象,那么编译器可能会优化,使两个对象合二为一,直接构造出一个对象。(这个现象看编译器,c++并没有这个标准,但是新的编译器一般都会优化)
同样的现象在这里还出现过,
A a2 = 3;
这里是隐式类型转换,A tmp(3),再用tmp拷贝构造一个 a2 . 一般编译器会优化了,直接进行构造,并不会调用拷贝构造。
还有一种情况如代码所示:
A f2()
{
static A aa;
这个对象变为局部静态变量,
在调用并运行到这一行时进行初始化,并且只在
第一次进入函数时构造,之后不会在构造。
不同于全局变量在main函数之前就进行初始化了。
return aa;
传值返回会返回一个拷贝的对象,相对的如果传引用返回,
则不会在进行一次拷贝构造。
}
上图情况就为返回值时的拷贝构造调用。但是当换一种情况,我们在main中不去使用拷贝构造,而是使用operator=
赋值运算符重载,那么就不会再进行优化了。如下图