文章目录
1. 函数重载
函数调用解析
如果在某个作用域范围内调用了某个重载函数,则C++编辑器会根据实参来决定调用函数的哪一个版本。为此,必须使用实参的个数和类型与重载函数的签名完全匹配。
以下编辑器确定调用哪一个重载函数的步骤:
- if,存在一个完全匹配的函数,则调用此函数。
- else if,通过标准的类型提升转换来进行匹配
- else if,通过转换函数或转换运算符来进行匹配。
- else if,判断是否可以通过省略号(…)进行匹配
- else,编译器报错。
2. 可选实参
默认(可选)实参
函数参数可以有默认值,此时它们就是可选的。可选实参的默认值可以是常量表达式或者不涉及局部变量的表达式。
拥有默认实参的参数必须是参数表中最靠右(最后面)的参数。具有默认值的最靠右实参在函数调用时可以忽略,而此时这些参数就会使用默认值进行初始化。
如果函数声明存放在一个单独的头文件中,则可选实参指示符不会出现在函数定义中,但调用时还会出现。
3. 运算符重载
C++使用关键字operator为运算符赋予新的含义。运算符重载提供了一种更紧凑的函数调用语法,可以在很大程度上提高代码的可读性。
Complex.h
class Complex
{
friend ostream& operator<< (ostream& out, const Complex& c);
friend Complex operator- (const Complex& c1, const Complex& c2);
friend Complex operator* (const Complex& c1, const Complex& c2);
friend Complex operator/ (const Complex& c1, const Complex& c2);
public:
Complex(double re = 0.0,double im = 0.0);
Complex& operator+= (const Complex& c);
Complex& operator-= (const Complex& c);
Complex operator+ (const Complex& c2);
private:
double m_Re,m_Im;
};
Complex.cpp
Complex::Complex(double re, double im)
{
m_Re = re;
m_Im = im;
}
Complex& Complex::operator+= (const Complex& c)
{
m_Re += c.m_Re;
m_Im += c.m_Im;
return *this;
}
Complex& Complex::operator-= (const Complex& c)
{
m_Re -= c.m_Re;
m_Im -= c.m_Im;
return *this;
}
ostream& operator<< (ostream& out,const Complex& c)
{
out << '(' << c.m_Re << ','<<c.m_Im << ')';
return out;
}
Complex Complex::operator+ (const Complex& c2)
{
return Complex(m_Re + c2.m_Re, m_Im + c2.m_Im);
}
Complex operator- (const Complex& c1, const Complex& c2)
{
return Complex(c1.m_Im - c2.m_Im,c1.m_Re - c2.m_Im);
}
Complex operator* (const Complex& c1, const Complex& c2)
{
return Complex(c1.m_Im * c2.m_Im,c1.m_Re * c2.m_Im);
}
成员运算符与全局运算符的比较: 成员运算符要求有一个作为左操作数的对象,而全局函数允许允许对任何一个操作数进行某种类型转换。对于Complex::operator+()
作为成员运算符的原因在于遵从原作,但是改为全局运算符也是可以的。
运算符重载存在一些限制。只有内置的运算符才能被重载,因此无法对类型与“$”、“ ‘ ”等未拥有运算符定义的符号重新进行定义。此外,它们的结合性和优先级无法改变。
可以重载除以下运算符之外的全部一元或者二元内置运算符:
- 三目运算符
testExpr ? valueIfTrue : valueIfFalse
- 作用域解析运算符
::
- 成员选择运算符
.
和.*
4. 按值传递参数
默认情况下,C++参数是按值传递的。当调用函数时,会产生每一个实参对象的一个临时(局部)副本并放入到程序栈中。只有这些临时副本在函数内部进行操作,而传递给函数的实参对象不会受这些操作的影响。当函数返回时,这些临时的栈变量就会被销毁。
如果将指针传递给函数,那么同样指针的一个临时副本会被放入到栈中。对这个指针的任何改变都不会对调用块中的指针产生影响。例如:
void bar(int* ptr)
{
*ptr = 34; 1
ptr = 0; 2
}
int main() {
int foo(3);
cout << foo << endl;
bar(&foo);
cout << foo << endl;
}
思考一下,打印的结果有什么不同,为什么不同?
第一行,操作的是指针指向的值。第二行,是操作指针本身。
具体指针的本身,是什么?指针的本身就是一个地址。而按值传递参数时,当函数返回时,指针的改变不会对函数传入的参数进行改变。同样可以通过cout << pn << endl;
来查看一下指针的值,发生什么改变?
5. 按引用传递参数
不应该将大型对象或者具有大量复制构造函数的对象进行按值传递,因为副本的创建会消耗大量不必要的资源。在C语言中,可以通过指针来传递对象,以避免这类对象的拷贝。此外,指针的误用会引起程序的崩溃,导致难以发现和修复的错误。在C++(以及C99)中,可以按引用传递参数,这种机制提供了与用指针传递参数相同的性能。
引用参数也是参数,只不过它是其它对象的一个别名。使用方式:在类型名和变量名中间添加一个&符号。eg:void foo(obj& o);
在使用别名是,必须对别名进行初始化,而且初始化的值必须是同类型的变量。eg:int& b = 1; int& foo = &foo; 都是错的。另外还有一个int& bar = int(1);还是错的。
按引用传递的语法提供了按指针传递的一种替代方式。本质上,按引用传递就是用指针访问实现的。二者的主要差别在于:对于指针,必须要解引用它(即使用 * 取到所执行的值);对于引用,可以向访问实体对象一样访问它的成员数据。而且还是使用的**.**运算符调用的哦。
6. const 引用
将一个引用参数声明为const,就是通知编译器,要确保函数不会试图改变这个对象。对于大于指针的对象,const 引用要比值参数高效,因为不需要进行数据复制。
一个需要注意的错误
void SetNameR(QString& newName) {m_name = newName;}
int main() {
....
setNameR("name"); //调用时出错,没有一个SetNameR(const char [5])的函数可以被调用。
...
}
7. 函数返回值
当执行完函数的执行代码时,有些函数会返回一个值。为返回对象临时准备的场所通常是一个寄存器,但是有时也可以是栈上分配的一个对象。当执行return语句时,会初始化临时的返回对象,且其存在时间只为满足包含该函数调用的任何表达式的使用,即将使用函数的返回值。对函数而言,这个对象副本通常是局部,或者是在return语句的表达式中构造的一个对象。
8. 从函数返回引用
当函数返回值时,会产生一个临时变量作为函数返回值的副本(保留该函数调用中要返回的值),而用引用返回值int& foo()
时,不产生值的副本。
如果返回值是引用的话,可以进行左值运算。
int& foo(int& a,int& b){
return a>b?a:b;
}
int a = 9,b = 5;
foo(a,b) = 6; // ==> a=6;
cout << a << endl;
参考于:c++函数返回引用
9. 基于const的重载
Class Foo {
int function();
int function() const;
};
可以看到类中函数function
函数发生了重载,且是合法的。在函数调用时,只有Foo类的const对象才能调用const版本的function函数,而非const函数通常调用的是非const版本的function函数。
原因是:按照函数重载的定义,函数名相同而形参列表不同的函数称为重载。在类中,由于隐含的this形参的存在,const版本的function函数使得作为形参的this指针的类型变为执行const对象的指针,而非const版本的使得形参this指针为正常版本的指针。重载函数在最佳匹配过程中,对于const对象就选取const版本的成员函数,而非const对象就调用非const版本的成员函数。(注:this指针是一个const指针,地址不能变,但可改变其指向的对象或者变量)
摘录于:C++ 学习之函数重载、基于const的重载
10. inline函数
为了避免函数调用带来的开销(例如:创建包含实参副本、引用参数地址以及返回地址的栈帧)。C++允许将函数声明为inline。这样的声明会要求编译器将所有对函数的调用都以函数完全展开后的代码进行替换。如:
inline int max(int a, int b) {
return a>b ? a:b;
}
int main() {
int temp = max(3,5);
...
}
扩展之后
int main() {
int temp;
{
int a = 3;
int b = 5;
tem = a>b ? a:b;
}
...
}
如果要重复地调用(例如:在一个大型循环中),则inline函数可极大的提高性能。但inline的缺点导致编译代码会变得更大。在运行时会占用更多的内存。对于小型的inline函数对内存虽然会用一定的影响,但潜在的性能收益会很大。
inline函数与#define
宏类似,但有一个重大差异:对#define
宏的替换过程是由预处理器完成的。对inline函数的替换过程是由编译器处理,它会执行更智能的操作,进行正确的类型检查。
inline函数的一些使用规则
inline函数是否实现取决于编译器的看法,依据:函数太复杂 (包含条件语句或超过一定数量的代码行) 则不被编译为inline
具体如何使用inline函数:
忘掉inline即可,不知道就可以不担心了.
摘录:如何知道一个函数在编译后是否被inline了?
11. inline函数与宏扩展的比较
宏扩展是一种通过预处理器指令来植入代码的机制。
不同于inline函数,宏扩展不对参数进行扩展。本质上,它是一种编辑操作。另外,在宏中必须注意圆括号的使用,以避免优先级错误。但是,圆括号也无法解决与宏有关的全部问题。
一般而言,应该避免使用代码替换宏。预处理器宏主要用于以下几种情况:
- 使用
#ifndef #define #endif
将头文件包裹起来,以避免多次包含某个头文件。 - 使用
#ifdef #else #endif
对某些代码部分进行条件编译。 __LINE__
:在源代码中插入当前源代码行号;__FILE__
:在源文件中插入当前源文件名;两宏用于调试并给出框架信息。
一般可以使用inline函数来代替宏的来进行代码替换。
12. 带变长实参表的函数
在C和C++中,可以定义其参数表以省略号结尾的函数。省略号使调用者能够指定参数的数量和类型。例如:int printf(char* formatstr,...)
。详细见: va_list原理及用法