C++面向对象 this指针 构造函数 析构函数 拷贝构造 友元

面向对象概念

对象的概念是面向对象技术的核心所在。面向对象技术中的对象就是现实世界中,某个具体的物理实体在计算机世界(逻辑)中的映射和体现。也就说计算机中的对象,是模拟现实世界中的实体。

类与对象的区别

类是设计的产物,设计是概念的产物 ,类不占有内存空间
对象是类的实例化,占有内存空间

C++中类的设计

设计实例

class CGoods{
private:
	char Name[21];int Amount;float Price;
	float Total_value;
public:
	void RegisterGoods(charint,f1oat);//输入数据
	void countTota1(void);
	//计算商品总价值
	void GetName(char[]);
	//读取商品名
	int GetAmount(void);
	//读取商品数量
	float GetPrice(void);
	//读取商品单价
	float GetTotal_value(void);
	//读取商品总价值
};

实例解释

共有和私有

四个数据成员被说明成私有,而六个函数成员被说明成公有;这就是说如果从外部对四个数据成员进行操作的话,只能通过六个公有函数来完成,数据受到了良好的保护,不易受外部环境的影响。

类的认识

类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字extern、auto或register限定其存储类型。
成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用函数成员。

函数定义

函数在类里定义和类外定义区别

在类里面声明,在类里面定义:系统默认该函数是inline函数,所以按照inline方式进行编译
在类里面声明,在类外面定义:该函数不是inline函数 ,如果想让其成为inline函数,就需要在类外定义该函数时,添加关键词inline。并且定义时,要在函数名前面加类型名::告诉编译器,该函数是该类的一个成员方法

函数定义实例

class CGoods{
private:
	char Name[21];int Amount;float Price;
	float Total_value;
public:
	void RegisterGoods(charint,f1oat);//输入数据
	void countTota1(void);//计算商品总价值
	{
		Tota1_value = Price  Amount;
	}
	void GetName(char[]);
	//读取商品名
	int GetAmount(void);
	//读取商品数量
	float GetPrice(void);
	//读取商品单价
	float GetTotal_value(void);
	//读取商品总价值
};
void cGoods ::RegisterGoods(const char_*name,int amount,float price){
	strcpy(Name,name);//字符串拷贝函数
	Amount = amount;
	Price = price;
}
//void cGoods : :countTota1(void){
//	Tota1_value = Price  Amount;
//}
void cGoods: :GetName(char name[]){
	strcpy(name,Name);
}
int cGoods:: GetAmount(void) { return (Amount); }
float CGoods ::GetPrice(void) { return (Price);}
//float cGoods::GetTotal_value(void) { return(Total_value); }

对象是类的实例(instance)。声明一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。类只是一个样板(图纸),以此样板可以在内存中开辟出同样结构的实例–对象。

C++对象模型

方案一:各对象完全独立地安排内存的方案

在这里插入图片描述
上图是系统为每一个对象分配了全套的内存,包括安放成员数据的数据区和安放成员函数的代码区。但是区别同一个类所实例化的对象,是由属性(数据成员)的值决定,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有的对象都是一样的。

方案二:各对象的代码区共用的方案:

在这里插入图片描述
上图仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象类共用。

this指针

this指针特点

this指针的特点是自身是一个常性指针,在类的成员函数都要添加this指针,只要是面向对象的语言,编译器都会进行一个修改

程序编译面向对象程序的过程

编译器针对程序员自己设计的类型分三次编译。
第一:识别和记录类体中属性的名称,类型和访问限定,与属性在类体中的位置无关。如class CGoods 中的Name,Amount,Price,Total_value;
第二:识别和记录类体中函数原型(返回类型+函数名+参数列表),形参的默认值,访问限定。不识别函数体。
第三:改写在类中定义函数的参数列表和函数体,改写对象调用成员函数的形式;

修改具体过程

给每一个成员函数加一个指向当前类型的this指针,该指针指向定义的对象的地址,该对象就是该类的一个实例化
函数里面只要是第一步进行类体属性识别过的属性,或者是类的方法成员,都要加this指针
形参不用加this指针
在调用函数(主函数)中,tea.RegisterGoods(“b1ack_tea”,12,560);实际上是把茶对象作为一个参数,调用该函数
//RegisterGoods(&tea,“black_tea”,12,560);
this指针指向的是tea的地址,然后填入后面的值
this指针是const指针,一旦调用了函数,指针自身不能修改,指向可以被修改

CGoods*const this

调用一次函数,this指向一个对象的地址,该函数调用完成,分配的栈帧释放,下次调用函数,this指针指向另一个对象的地址

前后代码对比

修改前

class CGoods{
private:
	char Name[21];
	int Amount;
	float price;
	float Tota1_va1ue;pub7ic:
public:
	void RegisterGoods(const char *name ,int,float);
	void countTota1(O);
};
void cGoods::RegisterGoods(const char *name ,int amount,float price){
	strcpy(Name,name);//字符串拷贝函数
	Amount = amount;
	Price = price;
}
void ccoods::countTota1(){
	Total_value = Price *Amount;
}
int main({
	cGoods tea;
	cGoods book;
	tea.RegisterGoods("b1ack_tea"12560);
	tea.countTota1();
	book.RegisterGoods( "Thinking In C++"20128);
	book.countTota1();
	return 0;
}

修改后

class CGoods{
private:
	char Name[21];
	int Amount;
	float price;
	float Tota1_va1ue;pub7ic:
public:
	//void RegisterGoods(CGoods*const this,const char *name ,int,float);
	void RegisterGoods(const char *name ,int,float);
	//void countTota1(CGoods*const this);
	void countTota1();
};
void cGoods::RegisterGoods(CGoods*const this,const char *name ,int amount,float price){
	strcpy(this->Name,name);//字符串拷贝函数
	this->Amount = amount;
	this->Price = price;
}
void ccoods::countTota1(CGoods*const this){
	this->Total_value = this->Price *this->Amount;
}
int main({
	cGoods tea;
	cGoods book;
	tea.RegisterGoods("b1ack_tea"12560);
	//RegisterGoods(&tea,"black_tea",12,560);
	tea.countTota1();
	//countTota1(&tea);
	book.RegisterGoods( "Thinking In C++"20128);
	//RegisterGoods( &book,"Thinking In C++",20,128);
	book.countTota1();
	//countTota1(&book);
	return 0;
}

全局函数和成员函数的区别

重点是this指针,其间的差别在于一个是程序的需要,一个是编译器的需要。

构造函数

C语言特点有空间即可操作
面向对象的特点有空间不一定有对象,有对象一定有空间

构造函数的由来

数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。称为构造函数。

构造函数用途–创建对象

这里其实是调用了无参的构造函数,只是将()省略了,Complex c1;如果不省略,会和函数声明发生二义性

Complex():real(0),image(0) {
		//real = 0; image = 0;
	}

初始化列表是在该空间构建整型数据,而一般的赋值是在构建之后,赋值而已

构造函数用途–初始化对象中的属性

#include<iostream>
#include<vector>
using namespace std;
class Complex {
private:
	int real; int image;
public:
	Complex():real(0),image(0) {
		//real = 0; image = 0;
	}
	Complex(int r, int i) {
		real = r;
		image = i;
	}
};
int main() {
	Complex c1;
	Complex c2(1,2); 
	return 0;
}

构造函数二义性

这里定义c1会产生二义性,可以调用无参的构造函数,也可以调用有参的构造函数,
只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。
只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,但是规定缺省的构造函数只能有一个。而这里有两个,所以编译不通过

#include<iostream>
#include<vector>
using namespace std;
class Complex {
private:
	int real; int image;
public:
	Complex():real(0),image(0) {
		//real = 0; image = 0;
	}
	Complex(int r=0, int i=0):real(r),image(i) {
		//real = r;
		//image = i;
	}
};
int main() {
	Complex c1;//error ,直接省略()
	Complex c2(1,2); 
	return 0;
}

数据成员的构建顺序

初始化列表的构建顺序,并不是数据成员的构建顺序,数据成员是按照类设计的顺序构建(是对象在内存中的顺序),所以需要先构造实部,real(image),此时image里面是随机值,所以real是随机值,image是10

#include<iostream>
#include<vector>
using namespace std;
class Complex {
private:
	int real; int image;
public:
	Complex(int x=0):image(x),real(image) {}
	Complex(int r, int i):real(r),image(i) {}
	void Print() { cout << "Real:" << real << "--" << "Image:" << image << endl; }
};
int main() {
	Complex c1(10);
	c1.Print();//随机值  10
	return 0;
}

类型转换

class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
}; 
int main() {
	Int a(10);
	Int b{ 20 };
	Int c = Int(30);
	
	int x = 200;
	a = x;
	return 0;
}

定义一个int x值为200,然后将x的值给Int(int x=0)里面的x,创建一个不具名对象,由构造函数实现
实际上他的写法为

a=(Int)x;

将x强转为Int,和C语言强转方式一样

关键字explicit

有一个关键字explicit,该关键字叫明确关键字,如果用该关键字在构造函数前面 ,则不能用该方式构建,即赋值方式

Int e = 100;//error

加了该关键字,就失去了隐式转换能力,必须强转

	a = x;//error
	a = (Int)x;
class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
}; 

int main() {
	Int a(10);
	Int b{ 20 };
	Int c = Int(30);
	Int d = Int{40};
	Int e = 100;
	int x = 200;
	a = x;
	a = (Int)x;
	return 0;
}

单参的函数才具有类型转换

这里无法进行类型强转


class Int {
private:
	int value;
public:
	Int(int x ,int y) :value(x+y) { cout << "create Int" << this << endl; }
}; 

int main() {
	Int a(1, 2);
	a=(100,200);//error
	return 0;
}

通过缺省函数,使其成为单参

class Int {
private:
	int value;
public:
	Int(int x ,int y=0) :value(x+y) { cout << "create Int" << this << endl; }
}; 

int main() {
	Int a(1, 2);
	a=(100,200);
	return 0;
}
class Int {
private:
	int value;
public:
	Int(int x ,int y=0) :value(x+y) { cout << "create Int" << this << endl; }
	void Print()const { cout << value << endl; }
}; 

int main() {
	Int a(1, 2);
	a = (Int)(100, 200);
	a.Print();//200
	a = Int(100, 200);
	a.Print();//300
	return 0;
}

a = (Int)(100, 200);这里他认为是要调用单参构造函数,逗号表达式取最右边的200,赋值给x,因为单参的构造函数才能进行强转的转换
a = Int(100, 200);直接调用

Int b()问题

如果给括号,一定给实参,没有给实参,一定将该圆括号删掉,或者给个{},因为Int b()这样,编译器并不认为这是构造b对象,而是认为了函数的声明,是因为C++兼容C语言,在C++11 初始化用{ }

class Int {
private:
	int value;
public:
	Int(int x=0) :value(x) { cout << "create Int" << this << endl; }
	void Print()const { cout << value << endl; }
}; 
int main() {
	Int a;
	Int b();//这个认为了函数的声明
	Int c{};
	return 0;
}

构造函数特征

构造函数是特殊的公有成员函数(在特殊用途中构造函数的访问限定可以定义成私有或保护),其特征如下:
1.函数名与类名相同。
⒉.构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不可写void。实际上构造函数有返回值,返回的就是构造函数所创建的对象。
3.在程序运行时,当新的对象被建立,该对象所属的类构造函数自动被调用,在该对象生存期中也只调用这一次。
4.构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。
5.构造函数可以在类中定义,也可以在类中声明,在类外定义。但是默认值必须在声明时给,不能在定义时给,否则会出现二义性
6.如果类说明中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数;类名(void){ }
构造函数也有this指针,改写,在编译的时候就可以确定类型的大小,
在调试的时候,没有赋值之前,全是随机值,空间有,没有对象

析构函数

析构函数的定义

当定义一个对象时,C++自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象并进行善后工作,这个特殊的成员函数即析构函数(destructor):

析构函数特点

1.构函数名与类名相同,但在前面加上字符~,如: ~CGoods () 。
⒉析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数
3.一个类有一个也只有一个析构函数,这与构造函数不同。
4.对象不能调用构造函数,但是,可以调用析构函数,
6对象注销时(主函数结束,释放栈帧),系统自动调用析构函数。
5.如果类说明中没有给出析构函数,则C++编译器自动给出一个缺省的析构函数。

全局变量和局部变量

静态量在数据区,不能重复建立
代码编译的时候关注的是可见性,代码运行起来关注生存期
进入主函数前,要创建全局变量

class object{
int value; public:
object(int x = 0) : value(x) {
	cout << "create object value : " << value << endl;
}
~object() {cout << "Destroy object value: " << value << endl;}
};
object obja(1); 
int main()
{
	object objb(2);
	//objectc.show();//加了这个程序直接无法编译通过
	return 0;
}
object objc(3);

在这里插入图片描述

class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
	~Int(){cout << "Destroy Int:" << this << endl;}
	//void func(Int *const this)
	void func() { cout << "Int func" << endl; }
	//void func(const Int *const this)
	void Print()const { cout <<"Int::value:" << value << endl; }
};
int main()
{
	Int* ip = nullptr;
	ip->func();//程序执行结果  Int func
	ip->print();//error  ip为空,空不能解引用
	return 0;
}

//这两等价
ip->func();func(ip);
ip本来就指向对象的地址,ip->func() 就是(ip).func();就是func(ip);
Int a;
a.func();//实际是func(Int
const this);该指针是指向a对象的地址,

class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
	~Int(){cout << "Destroy Int:" << this << endl;}
	void func() { cout << "Int func" << endl; }
	void Print()const { cout <<"Int::value:" << value << endl; }
};
int main()
{
	Int* ip = (Int*)malloc(sizeof(Int));
	if (nullptr == ip)exit(EXIT_FAILURE);
	ip->func();
	ip->Print();
	return 0;
}

打印随机值,有空间没有对象
在这里插入图片描述

class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
	~Int(){cout << "Destroy Int:" << this << endl;}
	void func() { cout << "Int func" << endl; }
	void Print()const { cout <<"Int::value:" << this->value << endl; }
};
int main()
{
	Int* ip = new Int(10);
	ip->Print();
	delete ip;
	ip = nullptr;
	return 0;
}

这里new的动作
1.sizeof(Int);
2.malloc();
3.构造对象;
4.return;
delete的动作
1.~Int()释放对象资源
2.free()将ip指向的空间还给系统
在这里插入图片描述

new一组对象,用delete[]删除一组对象
如果用delete 只能调用一次析构函数
开辟10个整型,在上下越界标记之间,用一个int标记创建对象个数,所以析构时,根据标记的值,依次析构
当我们设计类型时,这个类型有析构函数的时候,就会用4字节存放创建对象个数,当我们设计类型时,这个类型没有析构函数的时候,就不会在删除的时候调用析构函数,就不需要有这个计数值,就没有这个多出来的4字节
在这里插入图片描述
在这里插入图片描述

class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
	~Int(){cout << "Destroy Int:" << this << endl;}
	void func() { cout << "Int func" << endl; }
	void Print()const { cout <<"Int::value:" << this->value << endl; }
};
int main()
{
	int n = 0;
	Int* ip = nullptr;
	cin >> n;
	ip = new Int[n];
	Int* s = new Int(n);
	for (int i = 0; i < n; i++) {
		ip[i].Print();
	}
	delete[] ip;
	ip = nullptr;
	delete s;
	s = nullptr;
	return 0;
}

类型Int中有析构函数时,在堆区开辟内存中会用4个字节空间存放创建的对象个数,用于调用析构函数时,删除该数量的空间

常方法与对象的匹配关系

在函数后加const,修饰函数自身
当对象调用常方法时,只能读取对象的属性的值,只有只读权限,不能对对象属性修改,而可以修改非对象成员

//void Print(const Int*cosnt this)
void Print()const 

普通对象优先调用普通方法,如果普通方法不存在,退而求其次,调用常方法,如果是常对象,只能调用常方法
普通对象优先结合普通引用,如果普通引用不存在,退而求其次,结合常引用,如果是常对象,只能结合常引用

普通方法可以调用常方法,常方法不能调用普通方法

void func(){cout<<"func"<<value<<endl;}
void func() const {cout<<"func const"<<value<<endl;}
class Int
{
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }
	Int(const Int& it) :value(it.value) {}
	~Int() { cout << "Destroy Int: " << this << endl; }
	void Print() { cout << value << endl; }
	Int Add(Int b) {
		Int c;
		c.value = this->value + b.value;
		return c;
	}
	void SetValue(int x) { value = x; }
	int GetValue()const { return value; }

};

int main()
{
	Int a(10);
	int x = a.GetValue();
	a.SetValue(200);
	return 0;
}

上面的函数可以改成如下的样式,用一个函数实现了取值和设置值的作用,该Value函数返回的是引用,即别名
一个函数以引用的形式返回,他的特点是此数据的生存期不受此函数的影响,函数死了,该数据还活着

int&fun(){
	int x=10;
	return x;
}

这种返回是可以的,这种返回就是把一个死亡对象返回,函数死了,以引用返回的量不死亡

int&Value(){return value};
cosnt int&Value()const{return value};

用一个函数实现取值和修改值,用一个常方法再定义一遍,即可以对付普通对象,也可以对付常对象,使程序的健壮性更好

class Int
{
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }
	Int(const Int& it) :value(it.value) {}
	~Int() { cout << "Destroy Int: " << this << endl; }
	void Print() { cout << value << endl; }
	Int Add(Int b) {
		Int c;
		c.value = this->value + b.value;
		return c;
	}
	int& Value() { return value; }
};

int main()
{
	Int a(10);
	int x = a.Value();
	a.Value() = 200;
	return 0;
}
class Int
{
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }
	Int(const Int& it) :value(it.value) { cout << "Copy Create Int" << this << endl; }
	~Int() { cout << "Destroy Int: " << this << endl; }
	void Print() { cout << value << endl; }
	int& Value() { return value; }
};
void funa(Int x) {
	x.Value() = 100;
}
void funb(Int& y) {}
void func(const Int&z){}

int main() {
	Int a(10);
	funa(a);
	funb(a);
	func(a);
}

void funa(Int x)这种情况x是a的一份拷贝,其中调用拷贝构造函数,建立新的对象x,x的改变不影响a,不用用这种方式,效率问题,如果按照值的形式,如果效率会大大下降。传引用的话,引用在底层是一个指针,void funb(Intconst y)所以传引用比传值更快
void funb(Int& y) ,void funb(Int
const y)这种情况是y是a的一个别名,y的改变会影响a
void func(const Int&z) 这里只能读取,因为是常引用
如果你只想读取数据,不改变形参,就加上const,用常引用,如果想通过更改形参更改数据,就可以去掉const,用普通引用

Int func(int x) {
	Int tmp(x);
	return tmp;
}
Int fund(int x) {
	return Int(x);
}
int main() {
	Int a = func(100);
	Int b = fund(200);
	cout << a.Value() << endl;
}

Int func(int x)这里一共创建了两个对象,程序会对其有一个优化,将亡值对象不构建,直接构建a对象,所以最终,一个tmp对象,一个a对象
Int fund(int x) 这里一共创建了一个对象,就是b对象本身,也有优化
为了减少对象的创建一个,一般用Int fund(int x)这种方式

Int& func(int x) {
	Int tmp(x);
	return tmp;
}
int main() {
	Int a(10),b(20);
	a = func(100);
	cout << a.Value() << endl;
}

func栈释放后,以引用形式返回,tmp对象死亡了,但是残留在内存的数据还是在那里,从已死亡的对象取值,系统默认的赋值语句是几个汇编语句完成的,原来的func函数开辟的空间不会收到影响,仍然可以获取100,
如果我们在类里自己定义一个赋值语句,就会再次利用func的栈帧,就会填充,就会覆盖tmp的值。
所以不能以Int& 返回,从已死亡的对象抽数据

如果是静态区,对象不死亡,就可以捞数据,当第二次调用的时候,tmp就不会构建,只会构建一次

Int& func(int x) {
	static Int tmp(x);
	return tmp;
}

可以以引用的形式返回对象的情况
此对象的生存期不收该函数的影响,当函数调用结束后,此对象还活着,这种对象就是静态对象或者是全局对象,

拷贝构造函数

当拿一个对象初始化另一个对象的时候,调用拷贝构造函数
当函数调用时,形参和实参结合时,调用拷贝构造函数,
以值返回一个对象,需要建立一个将亡值对象,此时也要调用拷贝构造函数

Complex(const Complex& cx) :Real{ cx.Real }, Image{ cx.Image } {
	cout << "Copy Object:" << this << endl;;
}

在这里插入图片描述
这里Complexc2(c1)调用了拷贝构造函数,c3=func(c1),参数传递时,调用了拷贝构造函数,该函数返回的时候,以值的形式返回的时候,产生了一个将亡值,将结果赋值给将亡值,调用了拷贝构造函数

class Int
{
private:
	int value;
public:
	Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }
	Int(const Int& it) :value(it.value) {}
	~Int() { cout << "Destroy Int: " << this << endl; }
	void Print() { cout << value << endl; }
	//Int Add(const Int*const this,Int x)
	Int Add(const Int& x) const {
		return Int(this->value + x.value);
	}
};
int main() {
	Int a(10), b(20), c;
	a.Print();
	b.Print();
	c.Print();
	c = a.Add(b);
	c.Print();
}

Int Add(const Int& x) const {
	Int tmp;
	tmp.value=this->value+x.value;
	return tmp;//构建将亡值对象,
}

这里创建了两个对象,tmp和将亡值对象

Int Add(const Int& x) const {
	return Int(this->value + x.value);
}

这里创建了一个不具名对象(类型加括号),调用构造函数创建了一个对象
在这里插入图片描述

友元函数

友元函数作用与声明

友元函数存在的作用就是:友元friend机制允许一个类授权其他的函数访问它的非公有成员.友元函数可以访问这个类产生的对象的私有成员
友元声明:以关键字friend开头,它只能出现在类的声明中,它们不受其在类体中的public private和protected区的影响.就是说,声明放private public protected 里面都一样,无所谓。

友元函数的注意点:

1.不具有对称性︰A是B的友元,并不意味着B是A的友元。
2.不具有传递性︰A是B的友元,B是C的友元,但A不是C的友元。
3.不具有继承性: Base类型继承Object类型,如果Object类型是A的友元,但Base类型不是A友

友元函数的分类 外部函数友元,成员函数友元,类友元

外部函数友元

class Int {
private:
	int value;
public:
	Int(int x = 0) :value{ x } {	}
	~Int(){}
	friend void Print(const Int& it);
};
void Print(const Int& it) {
	//value = 20;//error
	cout << it.value << endl;
}
int main()
{
	Int a{ 10 };
	Print(a);
	//cout << a.value;//error
	return 0;
}

在这里插入图片描述

成员函数友元

class Int;
class Object {
public:
	void Print(const Int& it);
};
class Int {
private:
	int value;
public:
	Int(int x = 0) :value{ x } {	}
	~Int() {}
	friend void Object::Print(const Int& it);
};
void Object::Print(const Int& it) {
	cout << it.value << endl;
}
int main() {
	Object obj;
	Int a(10);
	obj.Print(a);
	return 0;
}

注意次序

类友元

class Int;
class Object {
public:
	void Print(const Int& it);
};
class Int {
	friend class Object;
private:
	int value;
public:
	Int(int x = 0) :value{ x } {	}
	~Int() {}
};
void Object::Print(const Int& it) {
	cout << it.value << endl;
}
int main() {
	Object obj;
	Int a(10);
	obj.Print(a);
	return 0;
}

在这里插入图片描述

静态关键字static:

static修饰局部变量

在c语言中用静态关键字修饰局部变量,
static int a = x;只在数据区定义一次,不会重复定义,即该代码只会被执行一次,

void fun(int x) {
	static int a = x;
	int b = 0;
	a += 1;
	b += 1;
	printf("a=%d,b=%d\n", a, b);
}
int main() {
	for (int i = 5; i > 0; i--) {
		fun(i);
	}
	return 0;
}

在这里插入图片描述

static修饰全局变量 函数

对于全局变量,加静态关键字,static int g_max=10;该变量只在本文件有效,在同一个工程下其他文件不可见 即 a.ccp 里面定义的,在b.cpp 不可见该变量

如果希望次函数只在本文件可见,在同一个工程下其他文件不可见,也可以直接加static

注意:

不要把形参修饰成静态量,编译器将忽略静态量修饰形参,仍然在栈上开辟
编译链接只对.cpp或者.c,不对.h编译链接

static在类里面修饰变量 与函数

在类里面用static申明,他就不是类所产生的对象的数据成员,它是这个类产生所有对象所共享的数据,申明并不意味着开辟空间,申明的静态成员在类外进行定义,开辟空间,并且初始化,调用的话用类名::
修饰函数的话,该函数也不属于哪个对象 是属于类的,调用的话用类名::调用 ,静态函数只能操作静态数据成员

extern

与static的极限拉扯

在main.cpp中,如果想用fun.cpp 中的int g_max = 10;就必须用extern关键字,说明该变量是同一个工程下的变量,使该处可见

extern int g_max;
int main() {
	g_max += 10;
	func();
}

在func.cpp中,如果你不想让g_max被引用,就可以加static修饰,就只能在本文件可见,及时在main.cpp中加extern关键字也不行

int g_max = 10;
void func(){}

加const会产生二义性

如果加const

const int g_max=0;
--------------------
extern const int g_max;
int main() {
	g_max += 10;
	func();
}

如果加const,这种情况是错误的,会产生二义性

const int g_max=0;
--------------------
extern const int g_max=10;
int main() {
	g_max += 10;
	func();
}

如果常变量在其他文件可以用,就加一个extern关键字,在其他文件用的时候加extern关键字

extern关键字用法总结:

静态数据成员
静态的目的是为所有的对象共享一个数据成员

在构造函数中不能拿参数列表对pi进行初始化,但是可以在构造函数赋值,因为所创建的静态量是所有对象共享的,它只有一份,他在类外已经被被构建了,不能再次构建,初始化列表就是在构建

using namespace std;
class Circle {
private:
	static double pi;//申明 .data
	double radius;
public:
	Circle(double r=0.0):radius(r){}
	~Circle(){}
	double area()const {
		return pi * radius * radius;
	}
	void showPI()const {
		cout << pi << endl;
	}
	void SetPI(double x) {
		pi = x;
	}
};
double Circle::pi = 3.14;//定义,INIT
int main() {
	Circle c1(10), c2(20), c3(30);
	cout << sizeof(c1) << endl;
	c1.showPI();
	c2.SetPI(3.1415926);
	c1.showPI();
	return 0;
}

在这里插入图片描述
有一个特殊性,只有char short int 可以
如果定义一个静态常量,就可以在类里面定义加赋值

class Object {
public:
	static const int num = 0;
};

或者这样,注意赋值的时候不要加static

class Object {
public:
	static const int num;
};
const int Object:: num = 0;
	//void func(const Circle *const this)
	void func()const {
		//this->radius += 10;//error
		pi += 10;
	}

在常方法里对非静态成员无法修改,因为用const修饰了this指针的指向,而静态成员可以修改,因为静态成员不在对象的空间中,在.data中 ,

在C语言中,有三个域,全局域,局部域,块域,而C++又新增加了一个类域

如果想要访问共有的静态成员的时,需要这样,就告诉编译器 ,pi属于这个类

Circle::pi = 10;

不能成为常方法的函数有全局函数,全局函数没有this指针,还有静态函数,
静态的函数没有this指针,所以就不能将静态函数定义为常方法

外部函数不能访问对象的私有成员,但是静态函数可以访问obj的私有成员,

using namespace std;
class Object {
private:
	static int num;
	int value;
public:
	Object(int x=0):value(x){}
	void print()const {
		cout << "num:" << num << "value" << value << endl;

	}
	static void show(Object& obj) {//not this
		cout << "num" << endl;
		cout << "value:" << obj.num << endl;
	}
};
int Object::num = 10;
int main() {
	Object obja(10), objb(20);
	obja.show(obja);
	obja.show(objb);
	Object::show(obja);
}

在这里插入图片描述

对象与对象的关系

依赖

#include<thread>
using namespace std;
class Book{};
class Food{};
class Human {
public:
	void Read(Book* pbook);
	void Eat(Food* pfood);
};
int main() {
	Book book;
	Food food;
	Human man;
	man.Read(&book);
	man.Eat(&food);
}

关联

class Teacher {
private:
	Course&cs;
public:
	//Teacher(){}
	Teacher(Course c):cs(c){}//error
};
int main() {
	Course ca, cb, cc;
	Teacher tyh(ca);
	return 0;
}

cs引用一个形参对象的别名,当函数销毁,形参也会销毁,cs会引用一个已经死亡的对象

class Teacher {
private:
	Course&cs;
public:
	//Teacher(){}
	Teacher(Course&c):cs(c){}//error
};
int main() {
	Course ca, cb, cc;
	Teacher tyh(ca);
	return 0;
}

加一个引用,c是ca的别名,再用c初始化cs,就是,cs是ca的别名

聚合

不存在生命的依赖

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
#include<iostream>
#include<thread>
using namespace std;
class Point {
private:
	float _x;
	float _y;
public:
	Point(float x=0,float y=0):_x(x),_y(y){}
	Point(const Point&) = default;
	Point& operator=(const Point&) = default;
	~Point(){}

};
class Circle {
private:
	Point _center;
	float _radius;
	static const float pi;
public:
	Circle(){}
};
const float Circle:: pi = 3.14;
int main() {
	Circle cir;
	Point p(1, 2);
	return 0;
}

组合

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
#include<iostream>
#include<thread>
using namespace std;
class Soul {};
class Body{};
class People {
private:
	Soul _soul;
	Body _body;
public:
	People(){}
};
int main() {
	People pe;
	return 0;
}
class Int {
private:
	int value;
public:
	Int(int x = 0) :value(x) {
		cout << "Create Int" << this << endl;
	}
	Int(const Int& it) :value(it.value) {
		cout << "Copy Create Int" << endl;
	}
	Int& operator=(const Int& it) {
		if (this != &it) {
			this->value = it.value;
		}
		cout << "this" << "operator=()  " << &it << endl;
		return *this;
	}
	~Int() {
		cout << "Destory Int:" << this << endl;
	}
	int& Value() {
		return value;
	}
	const int& Value()const {
		return value;
	}

};
class Object {
	Int num;
public:
	Object() { cout << "Create Object" << this << endl; }
	~Object() { cout << "Destroy Object;" << this << endl; }
};
int main() {
	Object obj;
	return 0;
}

先构建类的成员对象
在这里插入图片描述

Object() { cout << “Create Object” << this << endl; }
这里他会先调用Int的缺省构造函数,构建Int,再构建Object,如果没有Int的缺省构造函数,就会报错
有两个改动点可以解决

Object(int x=0):num(x) { cout << “Create Object” << this << endl; }
或者
在Int类里定义缺省构造函数
Int():value(0){}

析构和创建相反,先析构Ojbect再析构Int

成员对象Int有拷贝构造,Object没有拷贝构造,当Object objc(obja);时,调用Int的拷贝构造

成员对象Int没有拷贝构造,Object有拷贝构造,当Object objc(obja);时,调用Int的缺省构造函数,而无法调用成员对象的拷贝构造函数

成员对象Int有拷贝构造,Object有拷贝构造,当Object objc(obja); 时,调用Int的缺省构造函数,只有在Object中拷贝构造函数中给出明确的构造者

Object(const Object& obj) :num(obj.num) {
	cout << "Copy Create Object" << this << endl;
}

在这里插入图片描述
成员对象Int没有拷贝构造,Object没有拷贝构造,当Object objc(obja); 时,系统会合成缺省的拷贝构造,能实现成员对象的赋值

using namespace std;
class MyString {
private:
	struct StrNode {
		int ref;//引用计数
		int capa;
		int len;
		char data[0];
	};

	StrNode* pstr;
	StrNode* GetNode(int total) {
		StrNode* s = (StrNode*)malloc(sizeof(StrNode) + sizeof(char) * total);
		if (nullptr == s)exit(EXIT_FAILURE);
		return s;
	}
	void FreeNode(StrNode*p) {
		free(p);
	}
public:
	MyString(const char* p = nullptr) :pstr(nullptr) {
		if (p != nullptr) {
			int len = strlen(p);
			int total = len * 2;
			pstr = GetNode(total);
			pstr->ref = 1;
			pstr->len = len;
			pstr->capa = total - 1;
			strcpy(pstr->data, p);
		}
		cout << "Create MyString  " << this << endl;
	}
	MyString(const MyString& s) :pstr(s.pstr) { 
		if (pstr != nullptr) {
			pstr->ref += 1;
		}
		cout << "Copy Create MyString  " << this << endl;
	}
	MyString& operator=(const MyString& s) {
		if (this == &s || this->pstr == s.pstr)
		{
			return *this;
		}
		if (pstr != nullptr && --pstr->ref == 0) {
			FreeNode(pstr);
		}
		pstr = s.pstr;
		if (pstr != nullptr) {
			pstr->ref += 1;
		}
		cout << "operator=  " << this << endl;
		return *this;
	}
	void PrintString()const {
		if (pstr != nullptr) {
			cout << pstr->data << endl;
		}
	}
	MyString(MyString&& s) :pstr(s.pstr) {
		s.pstr = nullptr;
	}
	MyString& operator=(MyString&& s) {
		if (this == &s || 
			this->pstr == nullptr && this->pstr == s.pstr) {
			return *this;
		}
		if (pstr != nullptr && --pstr->ref == 0) {
			FreeNode(pstr);
		}
		pstr = s.pstr;
		s.pstr = nullptr;
		return *this;
	}

	~MyString() {
		if (pstr != nullptr && --pstr->ref == 0) {
			free(pstr);
		}
		pstr = nullptr;
		cout << "Destory MyString  " << this << endl;
	}
};
int main() {
	MyString s1("yhping");
	MyString s2(s1);
	s2.PrintString();
	return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值