重学c++程序设计(二):内联 & 函数重载 & 函数参数缺省 & 构造与析构

2021/6/8 20:27:58

本文主要是介绍重学c++程序设计(二):内联 & 函数重载 & 函数参数缺省 & 构造与析构,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

重学c++(二)

一、函数内联:

1.1、首先看函数调用过程:

函数调用是需要有额外的开销的,先把函数参数入栈,然后出栈跳转到函数体内执行,再把返回值入栈。这个开销只有几行指令,如果本身函数体比较复杂,那么这个几行指令的开销就显得微不足道了。但是如果本身函数体就很简单,总共还没几行指令,那么调用函数的指令就显得占比较大,多余了。尤其是在较简单的函数需要调用的次数很多的时候,那么这个开销就尤其大。于是,有了函数内联。

1.2、函数内联的实现方式:

编译器在处理内联函数的时候,并不会生成调用函数的执行指令,而是直接把内联的函数体,作为语句块插入到调用的地方。会在调用处获取输入的参数,然后另外开设局部变量来接收函数的返回值。

1.3、函数内联的语法规则:

只需要把内联关键字:inline加入到被内联的函数的函数类型声明之前,实例:

#include <iostream>
using namespace std;

inline int Max(int a, int b) { return a > b ? a : b; }
int main() {
	int a, b;
	cin >> a >> b;
	int k = Max(a, b);
	cout << k << endl;
	return 0;
}

在程序设计中,算法库的max()函数执行效率是很低下的,一般都是使用内联函数来代替它!然后我们来分析一下函数内联的编译器执行过程:

第一步:设定一个temp局部变量来接收内联函数返回值
第二步:if (a > b) temp = a;
第三步:else temp = b;
第四步:k = temp;

以如此的形式,完成了函数体的内联语句块插入!

二、函数重载:

2.1、重载函数的规则:

函数的名字相同,但是参数列表不同!返回值必须相同!这里的参数列表不同是体现在哪里呢?我们规定:参数的个数参数的数据类型不同,而函数名称相同的函数称之为重载函数。

看一个实例:
#include <iostream>
using namespace std;

inline int Max(int a, int b) { return a > b ? a : b; }
inline int Max(double a, double b) { return a > b ? a : b; }
inline int Max(char a, char b) { return a > b ? a : b; }
int main() {
	int a, b;
	cin >> a >> b;
	int k1 = Max(a, b);
	char s, t;
	cin >> s >> t;
	char k2 = Max(s, t);
	cout << k1 << ' ' << k2 << endl;
	return 0;
}

上面有三个Max()函数,它们的参数类型不同,也可让参数个数不同。
那么编译器是如何知道我们需要执行的是哪个函数呢?我们的编译器是根据我们传入的参数的类型和个数来确定需要执行的是哪个重载函数,并且在编译的时候,我们不同的重载函数其实也是有区别的,会生成不同的汇编程序。

三、函数参数缺省:

3.1、缺省的规则:

C++的函数定义是允许有参数缺省的,但是规则是:函数参数的最右边的若干个连续的参数带缺省值,也就是说,要让必要参数排在左边,因为我们在函数调用的时候,如果某参数是必要的,而中间某参数的缺省的,那么就会产生一种语法错误,例如定义函数:void f(int a, int b = 2, int c);如果我要调用函数,第二个参数缺省,那么就会出现这样子:f(1, , 3);这是绝对不允许的,是会报错的!

然后在调用带有缺省值函数的时候,缺省的地方会使用默认参数的值。

3.2、缺省的实例以及缺省的常用情景:

#include <iostream>
#include <cmath>
const double pi = acos(-1);
using namespace std;

double CircleArea(double r, double k = 1.0) { return pi * r * r * k; }
int main() {
	double r;
	cin >> r;
	cout << CircleArea(r) << endl << CircleArea(r, 2) << endl;
	return 0;
}

上述实例是在求一个圆形的面积的k倍,也许在工程中,我们最开始的项目需求是要求圆形的面积,但是后来项目需求更改了,需要能够让用户控制圆形的大小倍数,如果在C语言里面,我们更改函数参数,就必须在调用出同时更改实际参数列表。但是在大型工程里面这个重复性的工作量就比较大,所以C++产生了可缺省函数参数的机制,便于扩展项目。

四、构造函数:

4.1、构造函数的规则:

名字必须与类名相同,不可以有返回值!可以有参数也可以无参数,可以有多个构造函数。如果没有自定义的构造函数,系统才会给出一个什么都不干且没有参数的默认构造函数。在面向对象的程序设计中,对象是类的实例化,实例化必须通过构造函数实例化。构造函数必须是在对象声明的同时进行的,后续的赋值除非是类型转换的构造函数,否则不可以。

千万要注意:构造函数只能存在一个!

也就是说,一旦自己写了一个构造函数,那么系统默认的构造函数就将不复存在!

4.2、为什么要使用构造函数:

构造函数完成必要的构造工作,对象如果没有被构造,是绝对不可以使用的,否则会出运行错误!

4.3、对象的实例化方式:

对象的实例化有两种方式:

第一种:静态实例化方式:类名 对象名;//or 类名 对象名(初始化参数);
第二种:动态实例化:new的使用类名 *对象名 = new 类名(初始化参数);

注意啊,new出来的对象是一个指针。

4.4、构造函数在对象数组的使用:

也是两种方式:

第一种:类名 对象名[MaxSize] = {每个对象的初始化参数};
第二种:类名 *对象名 = new 类名[MaxSize];

4.5、构造函数的初始化列表:

初始化列表是构造函数的独有!可以提高程序的效率。
有一下几条注意:
第一:const成员变量必须由初始化列表进行初始化。
第二:初始化列表执行先于函数体,所以效率较高。
第三:成员变量的初始化顺序与成员在类中的声明顺序相同,与初始化列表中的位置无关
我的实例中用的都是初始化列表的形式,建议大家多多使用!

4.6、复制构造函数:

特点:

第一:函数只有一个参数,且为同类对象的引用,可以是常值也可不是
第二:如果没有定义复制构造函数,系统自动生成
第三:复制构造函数只有在被复制的对象初始化的时候才有效,如果是初始化之后,对象之间的赋值运算,那不算复制构造!

4.7、构造函数 & 复制构造函数的实例:

#include <iostream>
using namespace std;

class Sample {
private:
	int val;
public:
	Sample();
	Sample(int n);
	Sample(const Sample& s);
	int GetVal();
};

Sample::Sample() { std::cout << "Constructor1 is called!" << endl; }
Sample::Sample(int n) : val(n) { std::cout << "Constructor2 is called!" << endl; }
Sample::Sample(const Sample& s) : val(s.val) { std::cout << "Constructor3 is called!" << endl; }
int Sample::GetVal() { return val; }

void Test(Sample s) {  }

Sample CreateObj() { Sample s(123); return s; }

int main() {
	// 构造函数初始化对象数组
	Sample arr1[2];
	std::cout << "Step1" << endl;
	Sample arr2[2] = { 1, 2 };
	std::cout << "Step2" << endl;
	Sample *arr3 = new Sample[2];
	std::cout << "Step3" << endl;
	Sample* arr4[3] = { new Sample(4), new Sample(5) };
	std::cout << "Step4" << endl;
	Sample arr5[2] = { 7 };
	std::cout << "Step5" << endl;

	// 赋值构造函数起作用的三种情况:
	
	// 第一种情况:
	Sample OldEle = { 10 };
	Sample NewEle1 = Sample(OldEle);
	Sample NewEle2 = OldEle;
	std::cout << "Step6" << endl;

	// 第二种情况:
	/* 如果某个函数的参数是一个类的对象,则在调用函数的时候,传参会导致形参初始化,初始化方式是调用类的拷贝构造函数 */
	Sample TestS;
	Test(TestS);
	std::cout << "Step7" << endl;
	/* 对我们的启示是:从前我们常说,形参会把实参的内容信息拷贝,实际上不一定,因为可能拷贝构造函数并没有去拷贝实参
	的信息,这些取决于拷贝构造函数的函数操作 */

	// 第三种情况:
	/* 如果某个函数的返回值是一个类的对象,则这个返回值是用拷贝构造函数初始化的 */
	std::cout << CreateObj().GetVal() << endl;
	std::cout << "Step8" << endl;

	//注意:构造函数只会在初始化的时候调用,在初始化之后发生的所有对象之间的赋值,都不会调用赋值构造函数!
	return 0;
}

关于上述实例的说明尽在注释之中!!!

4.8、类型转换构造函数:

这个真的是牛逼,如果你的对象遭到了参数类型上不合适的实例化,我们可以借助explicit关键字,做到类型转换构造函数,实例如下:

#include <iostream>
using namespace std;

class Complex {
public:
	double real, imag;
public:
	Complex(double r, double i) :real(r), imag(i) {  }
	explicit Complex(int r, int i = 0) :real(r), imag(i) {  }
	
};
int main() {
	Complex c1(1.0, 2.0), c2(3.0, 4.0);
	c2 = Complex(9);// 这里切不可以写成:c2 = 9;
	cout << c2.real << ' ' << c2.imag << endl;
	return 0;
}

类型转换构造函数的底层原理:

它会先生成一个临时的对象,以转换后的属性类型实例化,然后把这个临时对象赋予给被转换的对象,然后这个临时对象自动消亡(析构)

五、析构函数:

5.1、注意的是:

new出来的对象,一定要用delete才能让它析构,否则不析构,而静态实例化的对象,在它的生存期结束的时候就会析构!
然后与构造函数不同的是:构造函数可以重载,而析构函数只能存在一个!
与构造函数相同的是:如果定义类的时候没写析构函数,那么系统会自动生成一个默认的析构函数,这个析构函数啥都不会做。

重要的应用场景:当类中自定义了构造函数,并且构造函数中使用了系统资源(如:堆空间、文件打开,等),则需要自定义析构函数

5.2、析构函数的功能:

析构函数会在每一个对象消亡后自动调用,可以用来作消亡前的善后工作,比如释放内存空间等等。特别注意的是,如果是对象数组,那么数组中的每一个只要是被创建的对象都会被析构。

5.3、析构函数的析构顺序:

栈对象的析构顺序是先构造的对象后析构。
如果是new出来的对象,那就是堆对象,只要被delete了,就会引发析构。所以析构顺序就是delete的顺序。
如果对象是函数参数,那么这个函数参数消亡,一般为函数结束自动回收,那么也会调用析构。

六、面向对象实战:

6.1、第一题:

在这里插入图片描述

解决方案:

#include <iostream>
using namespace std;

class Fract {
private:
	int num, den;
public:
	Fract(int n = 0, int d = 1) :num(n), den(d) {  }
	int ged(int x, int y);
	Fract add(Fract f);
	void show();
};

int Fract::ged(int x, int y) { return 0 == y ? x : ged(y, x % y); }

Fract Fract::add(Fract f) { 
	int Den = den / ged(den, f.den) * f.den;
	int Num = num * f.den / ged(den, f.den) + f.num * den / ged(den, f.den);
	Fract ans;
	ans.num = Num / ged(Num, Den);
	ans.den = Den / ged(Num, Den);
	return ans;
}

void Fract::show() { cout << num << '/' << den << endl; }

int main() {
	Fract f1(1, 5), f2(7, 20);
	Fract f3 = f1.add(f2);
	f3.show();
	return 0;
}

6.2、第二题:

在这里插入图片描述

解决方案:

#include <iostream>
using namespace std;

class ARRAY {
private:
	float a[10], b[10];
public:
	ARRAY(float c[]);
	void Process();
	void Print();
};

ARRAY::ARRAY(float c[10]) {
	for (int i = 0; i < 10; ++i)
		a[i] = c[i];
}

void ARRAY::Process() {
	for (int i = 0; i < 10; ++i) 
		b[i] = (a[(i - 1 + 10) % 10] + a[(i + 10) % 10] + a[(i + 1 + 10) % 10]) / 3.0;
}

void ARRAY::Print() {
	for (int i = 0; i < 10; ++i)
		cout << a[i] << ' ';
	cout << endl;
	for (int i = 0; i < 10; ++i)
		cout << b[i] << ' ';
}

int main() {
	float a[10] = { 0, 3, 6, 9, 12, 15, 18, 21, 24, 27 };
	ARRAY* obj = new ARRAY(a);
	obj->Process();
	obj->Print();
	return 0;
}

6.3、第三题:

在这里插入图片描述
在这里插入图片描述

解决方案:

#include <iostream>
using namespace std;

class ID {
private:
	int power[17] = { 7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2 };
	char check[12] = "10X98765432";
	char id[19] = {'\0'};
public:
	ID(char* str);
	void Fun();
	void Print();
};

ID::ID(char* str) {
	for (int i = 0; '\0' != str[i]; ++i)
		id[i] = str[i];
}

void ID::Fun() {
	for (int i = 16; i >= 7; i--)
		id[i] = id[i - 2];
	id[6] = '1', id[7] = '9';
	int Info = 0;
	for (int i = 0; i < 17; ++i)
		Info += (id[i] - '0') * power[i];
	id[17] = check[Info % 11];
}

void ID::Print() {
	cout << id << endl;
}

int main() {
	char str[] = "340524800101001";
	ID id = ID(str);
	cout << "原身份证号:";
	id.Print();
	cout << "新身份证号:";
	id.Fun();
	id.Print();
	return 0;
}


这篇关于重学c++程序设计(二):内联 & 函数重载 & 函数参数缺省 & 构造与析构的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程