C++ Primer Plus第八章总结与测试

内联函数

常规函数调用的执行过程:(1)执行到函数调用指令时,存储该指令的内存地址,并将函数参数复制到堆栈中(2)调到函数起点的内存单元开始执行,并将返回值放入寄存器中(3)执行完毕之后跳回到之前保存的指令处
内联函数的适用场合:函数长度短,被多次调用但不能是递归
使用方法:在函数的声明和定义前加上关键字 inline

内联与宏

#include<iostream>
#include<string>
using namespace std;
#define SQU(X) X * X

int main() {
    
    int a = 1;
    cout<<SQU(a + 2);
    system("pause");
    return 0;
}

这里的输出结果是5,因为#define并不是通过传递参数实现的,而是通过文本替换来实现的,将a + 2替换为X,所以真正参与计算的就是 1 + 2 * 1 + 2结果为5
如果使用C语言的宏执行了类似函数的功能,应该考虑换成C++的内联函数
inline double SQU(double x) {return x * x; }

引用

初始化

  1. 声明引用变量时必须进行初始化
  2. 初始化之后不允许修改

下面通过代码进行说明

#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;
}
生成的时机
  1. 实参的类型正确,但不是左值
  2. 实参的类型不正确,但可以进行转换
    左值指的是,可被引用的数据对象,例如,变量,数组元素,结构成员,引用和解除引用的指针,常规变量和const变量都可视为左值,但常规变量属于可修改的左值,const变量属于不可修改的左值
    实例参见如下代码,在vscode中可通过编译
#include<iostream>
using namespace std;

double test(const double& ra) {
    return ra * ra;
}
int main() {

    double side = 3.0;
    double* pd = &side;
    double& rd = side;
    long edge = 5L;
    double lens[4]{2.0, 5.0, 10.0, 12.0};
    double c1 = test(side); 
    double c2 = test(lens[2]);
    double c3 = test(rd);
    double c4 = test(*pd);
    double c5 = test(edge);//规则2
    double c6 = test(7.0);//规则1
    double c7 = test(side + 10.0);//规则1
    system("pause");
    return 0;
}

尽可能使用const

理由如下:

  1. 使用const可避免无意修改参数导致错误
  2. 使用const使函数能够处理const和非const实参,否则只能接受非const数据
  3. 使函数能正确生成并使用临时变量

引用于返回值

如果如书中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,意思就是匹配的重载函数太多了,找不到合适的.

另外:编译器在检查函数参数列表时,把引用类型和类型本身不做区分

重载引用参数

这对于不同引用类型的重载很有用
void sink(double& r1);
void sank(const double& r2);
void sunk(double && r3);
对于第一个sink函数,他只能和与可修改的左值参数匹配,第二个sank函数与可修改的左值,const左值,或者是右值参数匹配,第三个sunk函数与右值匹配

函数模板

关于模板类或者模板函数声明和定义的相关问题
特点:模板并不创建任何函数,只有在遇到了调用时,才会用指定类型去进行创建函数,开辟空间。最终的代码不包含任何模板,只包含了为程序而生成的是函数。使用模板的好处是,使多个函数定义更简单,更可靠
关于编译:在进行编译即 gcc -S时会进行编译,并生成一个汇编文件(以s结尾),此时关于模板的编译会进行两次:1.对模板本身的代码进行编译 2.对模板的调用代码进行编译

模板的局限性和具体化

局限性:

编写的模板函数可能无法处理某些类型,例如如下代码

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页有详细介绍

模板类和友元函数的正确打开方式

这些也是我在别的地方看到的,因为实验室项目没涉及到这里,所以没考虑过这个问题,正确的打开方式如下

#include<iostream>
using namespace std;

template<typename T>
class Person;

template<typename T>
void show(Person<T>& p);

template<typename T>
class Person {
public:
	Person(T age);
	friend void show<T>(Person<T>& p);
public:
	T mAge;
};

template<typename T>
Person<T>::Person(T age) {
	this->mAge = age;
}

template<typename T>
void show(Person<T>& p) {
	cout << p.mAge << endl;
}

int main() {
	Person<int> p(20);
	show(p);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴塞罗那的风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值