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

news2025/1/14 9:39:04

C++面向对象

  • 面向对象概念
    • 类与对象的区别
  • C++中类的设计
    • 设计实例
    • 实例解释
      • 共有和私有
      • 类的认识
    • 函数定义
      • 函数在类里定义和类外定义区别
      • 函数定义实例
    • C++对象模型
      • 方案一:各对象完全独立地安排内存的方案
      • 方案二:各对象的代码区共用的方案:
  • this指针
    • this指针特点
    • 程序编译面向对象程序的过程
    • 修改具体过程
    • 前后代码对比
      • 修改前
      • 修改后
    • 全局函数和成员函数的区别
  • 构造函数
    • 构造函数的由来
    • 构造函数用途--创建对象
    • 构造函数用途--初始化对象中的属性
    • 构造函数二义性
    • 数据成员的构建顺序
    • 类型转换
      • 关键字explicit
      • 单参的函数才具有类型转换
    • Int b()问题
    • 构造函数特征
  • 析构函数
    • 析构函数的定义
    • 析构函数特点
  • 全局变量和局部变量
    • 常方法与对象的匹配关系
  • 拷贝构造函数
  • 友元函数
  • 静态关键字:
  • 对象与对象的关系
    • 依赖
    • 关联
    • 聚合
    • 组合

面向对象概念

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

类与对象的区别

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

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区的影响.

友元分为外部函数友元,成员函数友元,类友元。
友元函数可以访问这个类产生的对象的私有成员
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;
}

在这里插入图片描述

静态关键字:

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

对于全局变量,加静态关键字,static int g_max=10;该变量只在本文件有效,在同一个工程下其他文件不可见

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

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;
}

在这里插入图片描述

不要把形参修饰成静态量,编译器将忽略静态量修饰形参,仍然在栈上开辟

编译链接只对.cpp或者.c,不对.h编译链接

在main.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 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关键字用法总结:

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

在类里面用static申明,他就不是类所产生的对象的数据成员,它时这个类产生所有对象所共享的数据,申明并不意味着开辟空间,申明的静态成员在类外进行定义,开辟空间,并且初始化

在构造函数中不能拿参数列表对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;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【力扣刷题 | 第十四天】

目录 前言&#xff1a; 7. 整数反转 - 力扣&#xff08;LeetCode&#xff09; 面试题 16.05. 阶乘尾数 - 力扣&#xff08;LeetCode&#xff09; 总结; 前言&#xff1a; 今天仍然是无固定类型刷题&#xff0c; 7. 整数反转 - 力扣&#xff08;LeetCode&#xff09; 给你…

傅氏变换算法

半局积分算法的局限性是要求采样的波形为正弦波。当被采样的模拟量不是正弦波而是一个周期性时间函数时&#xff0c;可采用傅氏变换算法。傅氏变换算法来自于傅里叶级数&#xff0c;即一个周期性函数I&#xff08;t&#xff09;可用傅里叶级数展开为各次谐波的正弦项和余弦项之…

D117-72. 编辑距离

题目描述 链接&#xff1a;添加链接描述 跟只考虑删除的完全一样&#xff0c;只不过是dp[i-1][j-1]1 class Solution:def minDistance(self, word1: str, word2: str) -> int:# dp[i][j]&#xff1a;以i-1为结尾的字符串word1&#xff0c;和以j-1位结尾的字符串word2&…

Qt 将某控件、图案绘制在最前面的方法,通过QGraphicsScene模块实现

文章目录 前言一、效果二、代码实现1.工程文件夹结构2.BackWidget类2.1 backwidget.h2.2 backwidget.cpp 总结 前言 在用Qt做一些应用的时候&#xff0c;有可能遇到和“绘制顺序”相关的问题&#xff0c;即要控制一些控件之间的显示前后问题&#xff0c;在常用的QWidget体系中&…

【数据结构与算法】力扣:翻转二叉树

翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[2,…

云HIS是什么?HIS系统为什么要上云?云HIS有哪些优点?

一、当前医疗行业HIS的现状与发展趋势 1.医院信息系统&#xff08;HIS&#xff09;经历了从手工到单机再到局域网的两个阶段&#xff0c;随着云计算、大数据新技术迅猛发展&#xff0c;基于云计算的医院信息系统将逐步取代传统局域网HIS , 以适应人们对医疗卫生服务越来越高的要…

BI-SQL丨角色和用户

角色和用户 在数仓的运维工作中&#xff0c;经常需要为用户开通不同权限的账号&#xff0c;使用户可以正常访问不同的数据&#xff0c;那么这就需要我们了解SQL Server的权限体系。 名词解释 登录名&#xff1a; 用来登录服务器的用户账号&#xff0c;例&#xff1a;sa&…

String类型

前言 之所以介绍是因为基本数据类型是系统中一切操作的基础&#xff0c;就像物理世界中的原子&#xff0c;高楼大厦中的砖瓦。当咱们整明白了这些基本数据类型&#xff0c;使用层面就是挑选和组合的问题了。本文小结下Redis中数据结构和使用场景&#xff0c;如果你有更骚气的挑…

Python中文件的读取

在Python中可以通过内置函数open()、read()和readline()实现文件的读取。 1 打开文件函数 1.1 open()函数的基本用法 open()是Python的内置函数&#xff0c;用来打开指定文件。该函数使用代码如下所示&#xff1a; fin open(words.txt) 其中&#xff0c;参数指定了要打开…

【软件设计师暴击考点】计算机网络知识高频考点暴击系列

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

NodeJS Request下载图片文件到本地⑩⑦

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言使用模块创建文件删除文件写入图片案例效果总结 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f…

网页前端制作需要哪些基础知识?

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言HTML基础知识1 HTM…

存款进阶“10万元门槛”,年轻人为何遭遇困境?

文章目录 ❗❗ 前言&#x1f48c;目前的存款在哪一个区间&#xff1f;你觉得存款难吗&#xff1f;&#x1f622;存钱到底有多难&#xff1f;&#x1f90d;为存款做出过哪些努力&#xff1f;&#x1f9e7;没钱更要理财吗&#xff1f;&#x1f194;影响年轻人存款能力和存款意愿的…

C++多态 动态联编 静态联编 虚函数 抽象类 final override关键字

C多态 多态多态原理 动态联编和静态联编纯虚函数和抽象类C11的final override关键字重载 隐藏 重写的区别 多态 1.派生类中定义虚函数必须与基类中的虚函数同名外&#xff0c;还必须同参数表&#xff0c;同返回类型。 否则被认为是同名覆盖&#xff0c;不具有多态性。 如基类中…

NCI Architecture

2.1 组成部分 NCI 可分为以下逻辑组件&#xff1a;  NCI 核心 NCI 核心定义了设备主机 (DH) 和 NFC 控制器 (NFCC) 之间通信的基本功能。 这使得 NFCC 和 DH 之间能够进行控制消息&#xff08;命令、响应和通知&#xff09;和数据消息交换。  传输映射 传输映射定义 N…

Excel百万级别数据的导入和导出【详细代码】

代码层级结构 DurationAspect package com.zhouyu.aspect;import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.A…

MFC 截图功能实现(2)

上篇文章里面所用的截图是点击按钮就截取当前打开的界面&#xff0c;链接&#xff1a; MFC 截图功能实现_春蕾夏荷_728297725的博客-CSDN博客 这里所用的截图是可以选中区域&#xff0c;另存为目录等的操作&#xff1a; 效果&#xff1a; 选中区域&#xff1a; 菜单&#xf…

javaee 任务调度,定时任务 多个任务同时执行

错误的写法 如果按照下图的写法&#xff0c;两个任务不会同时执行&#xff0c;因为是一个线程&#xff0c;两个任务是串行的关系。 可以看到第二个任务是等第一个任务执行完以后&#xff0c;才执行的。 正确的写法 使用线程池&#xff0c;为每一个任务创建一个线程 可以看…

Markdown中使用 LaTeX 编辑数学公式

Markdown中使用 LaTeX 编辑数学公式 1 介绍TeX&#xff08;计算机排版系统&#xff09;LaTeX&#xff08;TeX宏集&#xff09;KaTeX 和 MathJax 2 注意点单双美元符号包裹问题KaTeX 有些不支持 3 语法保留字符希腊字母希伯来字母二元运算符二元关系符几何符号逻辑符号集合符号箭…

Linux下一切皆文件的理解

目录 一. 回顾上文 Linux底层进程和被打开文件的映射关系图&#xff1a; Linux部分源代码示意图如下&#xff1a; ​编辑 二.Linux下一切皆文件的核心理解 一. 回顾上文 在前两篇文章中&#xff0c;我论述了Linux系统中关于文件基础IO的一些内容&#xff1a; 1.有关于文件…