Coursera_北大_C++_内联/构造/析构/静态/友元/this指针/常

本文详细介绍了C++中的内联函数、构造函数和析构函数的概念及用法。内联函数用于减少函数调用的开销,构造函数用于对象初始化,析构函数则在对象消亡时执行。此外,还讨论了函数重载、默认参数、复制构造函数、类型转化构造函数、友元以及this指针的应用。文章深入浅出地阐述了这些关键概念,帮助读者更好地理解和掌握C++编程基础。
摘要由CSDN通过智能技术生成

内联函数:当编译器访问的时候,把函数定义的语句直接插入到主语句。减少内存开销。
定义方法:

  1. inline + 成员函数
  2. 整个函数体出现在类定义内部。
class B{
	inline void func1(); //利用inline,定义内联函数。
	void func2(){};//整个func2()的函数放在类的内部也标志着内联函数。
};
void B::func1(){}//具体定义可以放在类外。

函数的重载
成员函数–带缺省参数。

#include <iostream>
using namespace std;
class Location{
	private :
		int x,y;
	public:
		void init(int x = 0, int y = 0); 
		void valueX(int val) {x = val;}//一个有参数
		int valueX(){return x;}//一个没参数。
		//valueX()就是函数的重载
};
void Location :: init(int X, int Y){
	x = X;
	y = Y;
}
int main(){
	Location A;
	A.init(5);//这个函数里面的y就使用了系统默认的y值,0。
	A.valueX(5);
	cout << A.valueX(); 
	//上面两个valueX()调用的是不同的函数
	return 0;
}

但也要防止程序的二义性。如:

void valueX(int val = 0){x = val;}
int valueX(){return x;}

A.valueX();
这个A.valueX()就无法判断该执行哪个。就会报错。

构造函数:

成员函数的一种,

  1. 名字与类名相同,可以有参数,不能返回值(void也不行)
  2. 作用是对对象进行初始化,如给成员变量赋初值。
  3. 如果定义类时没写构造函数,则编译器生成一个默认无参数的构造函数。
  4. 对象生成时,构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数。
  5. 一个类可以有多个构造函数。

个人猜测:就是赋予初始化的参数,python中的那些初值的定义在__init__里面定义的,但cpp没有,就来个构造函数。

构造函数的作用:

  1. 初始化工作,有了这个就不用专门再写初始化函数。(那个set_啥啥啥的)
  2. 有时对象没被初始化就使用,会导致程序出错。

来一个默认无参的构造函数:

class Complex{
	private:
		double real,imag;
	public:
		void Set(double r, double i);
}; // 编译器自动生成默认的构造函数

Complex c1;
Complex *pc = new Complex; // 也生成了对象,所以也用了默认的构造函数初始化。

来一个自己定义的构造函数:

class Complex{
	private:
		double real, imag;
	public:
		Complex( double r, double i = 0);
};
Complex :: Complex(double r, double i){
	real = r; imag = i;
}
//这时候如果来一个:
Complex c1; 
// 程序就会报错。因为缺少构造函数的参数。
Complex *pc = new Complex; //报错。
Complex c1(2); //这个是对的。
Complex *pc = new Complex(3,4); //也是对的。

来个多个构造函数的:

class Complex{
	private:
		double real, imag;
	public:
		void Set(double r, double i);
		Complex(double r, double i);
		Complex(double r);
		Complex(Complex c1, Complex c2);
};
Complex:: Complex(double r, double i)
{
	real = r; imag = i;
}
Complex:: Complex(double r)
{
	real = r; imag = 0;
}
Complex:: Complex(Complex c1, Complex c2);
{
	real = c1.real + c2.real;
	imag = c1.imag + c2.imag;
}
Complex c1(3),c2(1,0),c3(c1,c2);

上面三个构造函数是重载的,根据调用时候的参数决定。
所以 c1 就是实部3,虚部0。吊用了第二个。
c2是调用了第一个,c3调用了第三个。

构造函数与对象数组。

class CSample{
	int x;
public:
	CSample(){
		cout << "Constructor 1 Called" << endl;
	}
	CSample(int n){
		x = n;
		cout << "Constructor 2 Called" << endl;
	}
};
int main(){
	CSample array1[2]; // 生成了两个无参的对象 
	cout << "step1" << endl;//输出两个 Constructor 1 Called
	CSample array2[2] = {4,5};//构造了两个有参初始化的对象。
	cout << "step2" << endl;
	CSample array3[2] = {3};
	cout << "step3" << endl;
	CSample *array= new CSample[2]; // 没有交代参数,所以是无参。
	delete []array4;// new出来的要收回。
	return 0;
class Test{
	public:
		Test(int n){}//1
		Test(int n, int m){}//2
		Test(){}//3
}
Test array1[3] = {1,Test(1,2)};
//三个元素分别使用了第一个,第二个,第三个初始化。
//因为第一个是直接给了一个变量1,第二个是直接告诉他用Test(1,2),第三个是空的。
Test array2[3] = {Test(2,3),Test(1,2),1};
//用的是2 2 1
Test *parray3[3] = {new Test(4), new Test(1,2)};
// 指针数组对象可以不初始化,那么对象也没有被创建,因为它仅仅是指针。
// 必须使用这种 new + 对象名的形式,用new出来对象的地址,来初始化对象。
//但这个只生成了两个对象。最后一个元素 pArray[2] 没有对象生成。

1. 复制构造函数:
基本点:

  1. 只有一个参数
  2. 形如 X::X(X&)X::X(const X&)后者能用常量对象作为参数。
  3. 如果没有定义复制构造函数,编译器会生成默认的复制构造函数。
    如:
class Complex{
	private:
		double real,imag;
};
Complex c1;
Complex c2(c1);

有一个默认的无参构造函数,还有另一个默认的复制构造函数。将c2初始化称和c1一样。

如果自己定义了一个复制构造函数,那么默认的就不会存在。

class Complex{
	public:
		double real,imag;
	Complex(){}
	Complex (const Complex & c){
		real = c.real;
		imag = c.imag;
		cout << "Copy Constructor called";
	}
};
Complex c1;
Complex c2(c1);

不能有形如 X:X(X)的构造函数,也就是说参数不能是同类的对象。(所以才有复制构造函数)
基本作用:

  1. 初始化语句:
    Complex c2(c1);
    Complex c2 = c1;

  2. 如果某个函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用。

class A
{
	public:
	A();
	A(A&a){
		cout << "Copy constructor called" << endl;
		}
};
void fun(A a1){}
int main(){
	A a2;
	fun(a2);
	return 0;
}

这里的a1的复制构造函数是我们自己写的,任务并不是复制,所以形参a1的值就不会等于a2,程序输出结果为"Copy constructor called"。
如果我们自己没有写复制构造函数,使用系统默认的,那么a1的内容就会是a2拷贝。
3. 如果函数的返回值是类A的对象时,则函数返回时A的复制构造函数被调用。

class A
{
	public:
		int v;
		A(int n){v = n;};
		A(const A&a){
			v = a.v;
			cout << "Ccc" << endl;
		}
};
A Func(){
	A b(4);
	return b;
}
int main(){
	cout << Func().v << endl;
	return 0;
}

上程序的输出结果为
Ccc
4
上面那个Ccc是定义的复制构造函数的输出,下面的4是真正的Func().v。

2. 类型转化构造函数:
目的:实现类型的自动转换。
特点:只有一个参数,不是复制构造函数。

class Complex{
	public:
		double real,imag;
		Complex(int i){	//类型转换构造函数。和构造函数同名。
			cout << IntConstructor called" << endl;
			real = i; imag = 0;
		}
		Complex(double r, double i){
			real = r; imag = i;
		}
};
		
int main(){
	Complex c1(7,8);
	Complex c2 = 12;	//对c2进行初始化(不是赋值)不会生成一个临时对象,只会将12作为参数,传给Complex
	c1 = 9; // 这是一个赋值语句。编译器不会报错,因为编译器会调用类型转换构造函数,把9作为实参调用Complex(int i)函数。
	//把9自动转换成一个临时Complex对象。
	cout << c1.real << "," << c1.imag << endl;
	return 0;
}

析构函数:

成员函数的一种,
名字与类名相同,
在最前面加 “~”,
没有参数和返回值,
一个类最多只有一个析构函数。

定义类时没写析构函数,则编译器生成缺省析构函数。

class String{
	private:
		char *p;
	public:
		String(){
			p = new char[10];
		}
		~String();
};
String :: ~String(){
	delete []p; //对一个数组,要在前面用方括号如果只是 delete p;,那么智慧释放一个一个对象
}
class Ctest{
	public:
		~Ctest(){cout << "called" << endl;
};
int main(){
	Ctest array[2];
	cout << "end main" << endl;
	return 0;
}

结果时先输出"end main",因为有两个数组元素的对象,所以会出现两次“called"。

delete运算会导致析构函数的调用:

Ctest *pTest;
pTest = new Ctest;	//构造函数调用
delete pTest;		//析构函数调用
-----------------------
pTest = new Ctest[3];	//构造函数调用3次
delete []pTest;			//析构函数调用3次。

例子:

class Demo{
	int id;
public:
	Demo(int i)
	{
		id = i;
		cout << "id = "<< id << "Constructed" << endl;
	}
	~Demo()
	{
		cout << "id = " << id << "Destructed" << endl;
	}
};

Demo d1(1);						//全局变量,会调用一次构造函数。
void Func(){
	static Demo d2(2);	//静态,所以会在程序结束消亡
	Demo d3(3);		//在函数结束时消亡。
	cout << "Func" << endl;
}
int main(){
	Demo d4(4);		//会输出 id = 4 Constructed
	d4 = 6;		// 会调用系统默认的类型转化构造函数,所以会出现 id = 6 Consturcted 和 id = 6 Destructed
	cout << "main" << endl;
	{ Demo d5(5);}	//只会作用在花括号内。所以也是出现 id = 5 Constructed 和 id = 6 Destructed
	Func();
	cout << "main ends" << endl;
	return 0;
}

最终输出结果为:
id = 1 Constructed
id = 4 Constructed
id = 6 Constructed
id = 6 Destructed
main
id = 5 Constructed
id = 5 Donstructed
id = 2 Constructed
id = 3 Constructed
Func
id = 3 Destructed
main ends
id = 6 Destructed
id = 2 Destructed
id = 1 Destructed
注意,最先构造的对象会最后消亡。

静态成员变量,静态成员函数
前面加了 static 关键字的成员。
对象中的静态成员对象被所有对象共享。不属于固定某一个。
sizeof 不会加上静态成员变量的大小。

访问静态成员:
类名::成员名 访问静态成员。CRectangle::PrintTotal();
对象名.成员名 CRectangle; r.PrintTotal(); (但他并不属于r这个对象。)
指针 -> 成员名CRectangle *p = &r; p->PrintTotal();
引用.成员名 CRectangle & ref = r; int n = ref.nTotalNumber;

静态成员变量本质上是全局变量。
目的是把和某些类紧密相关的全局变量和函数写到类里面,增加可读性和修改性。

静态成员变量要单独拿出来声明一下。
如:

int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalArea = 0;

在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。(因为非静态成员函数不属于任何一个对象,所以它也不知道w到底是属于谁的,就会报错。)

CRectangle::CRectangle(int w_,int h_)//构造函数
{
	w = w_;
	h = h_;
	nTotalNumber ++;
	nTotalArea += w*h;
}
CRectangle::~CRectangle()//析构函数
{
	nTotalNumber --;
	nTotalArea -= w*h;
}
void CRectangle::PrintTotal()
{
	cout << nTotalNumber << "," << nTotalArea << endl;
}

上代码容易出错,因为CRectangle类可能是复制构造函数生成的。(就是以该对象作为函数的参数,函数的返回值的情况。),没有增加总数(没有经过构造函数)却减少了总数(因为调用了析构函数)。
解决方法就是编写复制构造函数,让CRectangle类在复制构造函数中除了赋值还要增加总数和增加总面积。

成员对象:(之前提到的都是成员变量和成员函数)
成员对象是一个类的成员变量是另一个类的对象。
包含成员对象的类叫做封闭类。(类和类是能交织一起的)

class CTyre{	//轮胎类
	private:
		int radius;
		int width;
	public:
		CTyre(int r, int w):radius(r),width(w){}	//构造函数。这样的初始化方式更好看一些。
};
class CEngine{		//轮胎类
};
class CCar{
	private:
		int price;
		CTyre tyre;
		CEngine engine;
	public;
		CCar(int p, int tr, int tw);
};
CCar :: CCar(int p, int tr, int w):price(p),tyre(tr,w){	// 构造函数。
};

int main(){
	CCar car(20000,17,225);
	return 0;
}

如果CCar类不定义构造函数就会出错。因为编译器不知道car.tyre该如何初始化。
当封闭类对象生成时,先执行所有成员对象的构造函数,再执行封闭类的构造函数。与成员初始化列表中出现的顺序无关。
封闭类的对象消亡时,先执行封闭类的析构函数,再执行成员对象的析构函数。(先生成的后消亡)

友元:
友元函数:
一个类的友元函数可以访问该类的私有成员。

class CCar;		//要在CDriver之前声明一下。
class CDriver{
	public:
		void ModifyCar(CCar * pCar);
};
class CCar{
	private:
		int price:
	friend int MostExpensiveCar(CCar cars[], int total);		//这个函数是成员函数之外的函数,声明为友元。
	friend void CDriver::ModifyCar(CCar *pCar);//声明其他类的成员函数为友元。
};
void CDriver::ModifyCar(CCar *pCar)
{
	pCar->price += 1000;	//因为声明过为友元,所以可以访问CCar里的price。
}
int MostExpensiveCar(CCar cars[],int total)
{
	int tmpMax = -1;
	for( int i = 0; i<total; ++i)
		if(car[i].price > tmpMax)
			tmpMax = cars[i].price;
		return tmpMax;
}
int main()
{
	return 0;
}

注意,将一个类的成员函数作为另一个类的友元时,是包括构造,析构函数的。(就是上面才CCar中的CDriver的modify那个函数)

友元类:
如果A是B的友元类,那么A的成员函数能访问B的私有成员。
如:

class CCar{
	private:
		int price;
	friend class CDriver;
};
class CDriver{
	public:
		CCar myCar;
		void ModifyCar(){
			myCar.price += 1000;
		}
};
int main(){return 0;}

这个函数中在CCar类中声明了CDriver为友元类,所有在CDriver类中就能访问到CCar类中的私有成员price。

但友元类之间不能传递。

this指针:

指向成员函数所作用的对象。

class Complex{
	public:
		double real,imag;
		void Print(){ cout << real << "," << imag;}
		Complex(double r,double i):real(r),imag(i)
		{}
		Complex AddOne(){
			this -> real++;	//等价于real++
			this -> Print();//等价于Print();
			return *this;	//相当于返回操作后的原对象。
		}
};
int main(){
	Complex c1(1,1),c2(0,0);
	c2 = c1.AddOne();
	return 0;
}	//输出2,1
class A
{
	int i;
public:
	void Hello(){cout << "Hello" << endl;}
};	//机器翻译后是 void Hello(A *this){cout << "Hello" << endl;
int main()
{
	A *p = NULL;
	p->Hello();
}
//这个程序不会出做,会输出Hello。
//因为p->Hello();会被翻译成Hello(p),然后就相当于调用了一次Hello(p),会输出"Hello"虽然p指针式空的。

但是下面这个就会出错:

class A{
	int i;
public:
	void Hello(){cout << i << "Hello" << endl;}
};
int main(){
	A *p = NULL;
	p -> Hello();

静态成员函数不能使用this指针。
因为静态成员函数并不能具体作用于某个对象。
所以静态成员函数的真是参数的个数就是程序中写出参数的个数。

常量对象、常量成员函数、常引用:

  1. 如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字。

  2. 在类的成员函数说明后面可以加const关键字,则该成员函数称为常量成员函数。
    常量成员函数执行期间不应修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量(静态成员函数除外)。

class Sample{
	public:
		int value;
		void GetValue() const;
		void func(){};
		Sample(){}
};
void Sample::GetValue() const
{
	value = 0;	//error 因为常量成员函数不能修改作用的变量的值。
	func();	//出错,不能调用其他的非常量成员函数。因为它可能改变对象的值。
}
int main(){
	const Sample o;
	o.value = 100;//error 常量对象不能被修改
	o.func();	//error 常量对象上面不能执行非常量成员函数。
	o.GetValue();	//可以,常量对象上可以执行常量成员函数。
	return 0;
}

如果两个成员函数,名字和参数都一样,但是一个是const,另一个不是,算重载。

class CTest{
	private:
		int n;
	public:
		CTest(){n = 1;}
		int GetValue const {return n;}
		int GetValue {return 2*n}
}
int main(){
	const CTest obj1;
	CTest obj2;
	cout << obj1.GetValue() << "," << obj2.GetValue();
	return 0;
//输出结果一个是:1 2

引用前加const关键字,成为常引用。
对象作为函数参数,生成参数对象调用复制构造函数,效率低。指针作参数不够美观。
所以用对象的引用作为参数。但函数中修改形参同时也会修改外面对象的值,所以可以用常引用作为参数,如:

class Sample{...};
void PrintfObj(const Sample & o){...}
//语句中不会出现修改o的值的语句。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值