常规函数调用的执行过程:(1)执行到函数调用指令时,存储该指令的内存地址,并将函数参数复制到堆栈中(2)调到函数起点的内存单元开始执行,并将返回值放入寄存器中(3)执行完毕之后跳回到之前保存的指令处 内联函数的适用场合:函数长度短,被多次调用但不能是递归 使用方法:在函数的声明和定义前加上关键字 inline
这里的输出结果是5,因为#define并不是通过传递参数实现的,而是通过文本替换来实现的,将a + 2替换为X,所以真正参与计算的就是 1 + 2 * 1 + 2结果为5 如果使用C语言的宏执行了类似函数的功能,应该考虑换成C++的内联函数 inline double SQU(double x) {return x * x; }
下面通过代码进行说明
#include<iostream> using namespace std; int main() { int rats = 101; int & rodents = rats; cout<<"rats address:"<<&rats<<" value:"<<rats<<endl; cout<<"rodents address:"<<&rodents<<" value:"<<rodents<<endl; int bunnies = 50; rodents = bunnies; cout<<"after change\n"; cout<<"rats address:"<<&rats<<" value:"<<rats<<endl; cout<<"rodents address:"<<&rodents<<" value:"<<rodents<<endl; cout<<"bunnies address:"<<&bunnies<<" bunnies:"<<bunnies<<endl; rodents++; cout<<"after add\n"; cout<<"rats address:"<<&rats<<" value:"<<rats<<endl; cout<<"rodents address:"<<&rodents<<" value:"<<rodents<<endl; cout<<"bunnies address:"<<&bunnies<<" bunnies:"<<bunnies<<endl; system("pause"); return 0; } 对应的输出为 rats address:0x61fe14 value:101 rodents address:0x61fe14 value:101 after change rats address:0x61fe14 value:50 rodents address:0x61fe14 value:50 bunnies address:0x61fe10 bunnies:50 after add rats address:0x61fe14 value:51 rodents address:0x61fe14 value:51 bunnies address:0x61fe10 bunnies:50从上面的输出中我们可以看到,虽然乍一看我们让rodents重新赋值为bunnies,但是当我们再进行输出以后发现rats和rodents的值都发生了改变,并且二者地址一样且皆不同于bunnies,类似于一个const型的指针
我们都知道,如果要编写函数交换两个变量的值,函数的参数需要是指针,现在也可以通过传递引用实现同样的效果 那么关于按值传递,按址传递以及按引用传递三者的不同点在哪里呢? 还是通过代码进行解释
#include<iostream> using namespace std; void swap_value(int a1, int b1) { cout<<"swap_value a1 address:"<<&a1<<" b1 address:"<<&b1<<endl; swap(a1, b1); } void swap_reference(int& a1, int& b1) { cout<<"swap_reference a1 address:"<<&a1<<" b1 address:"<<&b1<<endl; swap(a1, b1); } void swap_point(int* a1, int* b1) { cout<<"swap_point a1 address:"<<&a1<<" b1 address:"<<&b1<<endl; swap(*a1, *b1); } int main() { int a = 10, b = 20; cout<<"a address:"<<&a<<" b address:"<<&b<<endl; cout<<"a:"<<a<<" b:"<<b<<endl; swap_value(a, b); cout<<"after swap_value:"<<"a:"<<a<<" b:"<<b<<endl; swap_reference(a, b); cout<<"after swap_reference:"<<"a:"<<a<<" b:"<<b<<endl; swap_point(&a, &b); cout<<"after swap_point:"<<"a:"<<a<<" b:"<<b<<endl; system("pause"); return 0; } 对应的输出是: a address:0x61fe1c b address:0x61fe18 a:10 b:20 swap_value a1 address:0x61fdf0 b1 address:0x61fdf8 after swap_value:a:10 b:20 swap_reference a1 address:0x61fe1c b1 address:0x61fe18 after swap_reference:a:20 b:10 swap_point a1 address:0x61fdf0 b1 address:0x61fdf8 after swap_point:a:10 b:20通过上面的代码我们可以清楚地发现,当我们调用swap_reference时,用a和b两个实参去初始化两个引用型变量的形参,由于引用就是一个别名,所以这里可以看到&a1 == &a, &b1 == &b,因为他们本质上都是一个内存地址,只不过是不同的名字,但是当我们调用swap_point时,就会发现虽然他也可以完成交换功能,但实际上还是会在栈区开辟出两个指针型的变量的空间,分别用a和b的地址对其进行初始化,所以这里&a1 != &a, &b1 != &b
如果实参与引用参数不匹配,C++将生成临时变量,当且仅当参数为const的引用时
#include<iostream> using namespace std; void test(const int& a) { cout << "666\n"; } void tes2t(int& a) { cout << "666\n"; } int main() { test(6.0); //ok test2(6.0); //无法通过编译 system("pause"); return 0; }理由如下:
使用const可避免无意修改参数导致错误使用const使函数能够处理const和非const实参,否则只能接受非const数据使函数能正确生成并使用临时变量如果如书中P266所说,如果返回值类型是一个对象,那么会把整个结构复制到一个临时位置,再将这个拷贝复制给要承接这个返回值的变量中,还是通过代码来举例子
#include<iostream> using namespace std; class student { public: int number; student(const student& stu) { cout << "copy instruct called\n"; } student() { cout << "instruct called\n"; } student test(); }; student student::test() { student st; return st; } int main() { student st; student st1(st.test()); student st2 = st.test(); system("pause"); return 0; }这里我只是简单的重写了拷贝构造函数,上面这段代码是为了讲解当函数返回一个对象时会发生什么,对于vs2019来说,会调用拷贝构造函数,但是对于vscode来说则不会这么做 所以输出结果如下
vs2019的输出 instruct called //初始化st instruct called //st.test时生成的对象 copy instruct called //用st.test生成的对象去初始化st1 instruct called //st.test时生成的对象 copy instruct called //用st.test生成的对象去初始化st2 vscode的输出 instruct called instruct called instruct called关于C++中拷贝构造函数的调用时机,参考这篇文章
传统的返回机制与按值传递参数类似,计算关键字return后面的表达式,并将结构复制到一个临时的位置,再将这个结果拷贝给承接他的地方,但当返回值为引用时,直接把返回值复制到承接的变量中(可能会调用拷贝构造函数或者是赋值构造函数) 书P267 对于按值传递函数参数来说,会创建一个局部变量用实参去初始化,待函数结束之后会进行空间的释放,也就是在栈上开辟的临时空间被实参初始化,函数结束再出栈 书P207 总的来说就是,返回引用的效率要高一些 注意点:返回引用的函数实际上是被引用的变量的别名,而且要注意不要返回指向临时变量的引用,因为函数执行完之后临时变量将不复存在,那还引用个锤子,试图引用已释放的内存,这就很离谱
假设实参的类型与引用类型不匹配,但可被转化为引用类型,程序将创建一个正确类型的临时变量,使用转换后的实参来初始化形参
注意点1:只在声明中使用默认参数 注意点2:必须从右向左添加默认值,因为形参和实参匹配是从左到右的
函数名称相同,但是参数数目或者参数类型不同
在使用被重载的函数时,需要在函数调用中使用正确的参数类型,说明代码如下
#include<iostream> using namespace std; void print(int i, int j) { cout <<"function 1\n"; } void print(double i, int j) { cout <<"function 1\n"; } void print(long i, int j) { cout <<"function 1\n"; } int main() { unsigned int a = 10; print(a, 6); system("pause"); return 0; }这时无法进行任何匹配,因为这三个print均可把a进行转换,vscode中报错:error: call of overloaded ‘print(unsigned int&, int)’ is ambiguous,意思就是匹配的重载函数太多了,找不到合适的.
另外:编译器在检查函数参数列表时,把引用类型和类型本身不做区分
关于模板类或者模板函数声明和定义的相关问题 特点:模板并不创建任何函数,只有在遇到了调用时,才会用指定类型去进行创建函数,开辟空间。最终的代码不包含任何模板,只包含了为程序而生成的是奇函数。使用模板的好处是,使多个函数定义更简单,更可靠
编写的模板函数可能无法处理某些类型,例如如下代码
struct student { int a, b; }; template<typename T> void SWAP(T& t1, T& t2) { t1 += t2; } student stu1, stu2; SWAP(stu1, stu2); 这个时候如何进行加法操作解决方法有两种,第一个是重载相应的操作符,第二种就是模板的显式具体化
可以提供一个具体化的函数定义–称为显式具体化,其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,直接使用不再去寻找模板 对于上面的例子,对应的显式具体化代码为, template关键字后要有一对尖括号
template<> void SWAP<student> (student& st1, student& st2) { st1.a += st2.a; st1.b += st2.b; }代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。在上面的例子中,如果我们通过如下代码 int a = 10, b = 2; SWAP(a, b); 那么将导致编译器生成SWAP()的一个实例,该实例使用int类型。模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式称为隐式实例化。
与之对应的就有显式实例化,他的语法很简单 template void SWAP(int, int) 注意这里template后面是没有尖括号的
还可通过在程序中使用函数来创建显式实例化 template<typename T> T Add(Ta, Tb) { return a + b; } int m = 6; double x = 10.2; cout << Add<double>(x, m) << endl; 这里的模板与函数调用Add(x, m)不匹配, 因为该模板要求两个函数参数的类型相同。 但通过使用Add<double>(x, m)可强制为double类型实例化, 并将参数m强制转换为double类型但这种做法如果是 int m = 5; double x = 10.2; SWAP< double>(m, x); 这样写将为double生成一个显式实例化,但是有SWAP指定的形参为引用类型,无法指向int变量,原因在上面关于临时变量的创建中有提到
隐式实例化,显示实例化和显式具体化统称为具体化 优先级:非模板函数 > 具体化 > 常规模板
指的就是找出最具体模板的规则,这部分内容书上290页有详细介绍