前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >C++ 类和对象

C++ 类和对象

作者头像
发布2024-11-24 08:27:41
发布2024-11-24 08:27:41
8400
代码可运行
举报
文章被收录于专栏:转自CSDN转自CSDN
运行总次数:0
代码可运行

类的定义

定义格式

class为定义类的关键字,stack,list,vector,queue等都是类的名字,而{}内的东西是类的类的主体。类体中内容被称为类的成员:类中的变量为类的属性或者成员变量;类内的函数时类的方法和成员函数。

C++中struct也可以定义类,但是struct同C语言相比是升级成了类,和class的用法几乎类似。

定义在类里面的成员函数默认为inline

代码语言:javascript
代码运行次数:0
复制
class cl
{
	void number()
	{

	}

	int _number;
};
struct c2
{
	void number()
	{

	}

	int _number;
};

访问限定符

C++实现封装的一种方式,是用类将对象的属性及其相对应的方法集合在一起,让对象更完善,通过特定的访问方式权限让用户访问和使用。

访问限定符有三个{ public , private , protected }

public是公开类,可以直接被使用,但protected和private修饰的成员在类外不能直接被访问,protect和private基本一样。

访问权限作用域从该修饰词出现到下一个修饰词的范围内,或者结束到“ } ”(类结束)。

class默认是private,struct默认为pubilc。

代码语言:javascript
代码运行次数:0
复制
class cl
{
public:
	void number()
	{

	}
private:
	int _number;
protected:
	int _t;
};

类域

类定义了一个新的作用域,类的所有成员都在该作用域中,在类外定义成员时,需要使用" :: "进行访问(指明类域)

类域影响的是编译的查找规则,如果没有指定类域,编译器会在全局域中寻找,如果编译的时候找不到声明或者定义,就会报错。

代码语言:javascript
代码运行次数:0
复制
class cl
{
public:
	void number()
	{

	}
private:
	int _number;
protected:
	int _t;
};

int main()
{
	cl A;
	A.number();
}

实例化

概念

用类在物理内存中创建对象的过程,被称为类的实例化。

类对对象进行一种抽象描述,分配在class(struct)中的函数,变量,只是一种模型,必须要有实例化,实例化出对象,才会分配空间。

类可以认为是一种复制用的设计图,可以根据它实例出很多对象,从而占据实际物理空间。

代码语言:javascript
代码运行次数:0
复制
class cl
{
public:
	void number()
	{

	}
private://在这里仅仅声明了定义罢了 没有在这分配空间
	int _number;
protected:
	int _t;
};

int main()
{
	cl A;//在这里实例化了 对A按照模板分配的空间
	A.number();
}

对象大小

首先 类成员 和 类成员函数 是分配在分别的空间的,可以认为在访问对象的成员是去独立分配的空间中找,但是类成员函数存在公共代码区,它们公用一个空间。

C++给定实例化对象也要符合内存对齐规则。

对齐规则

第一个成员在结构体偏移量为0的位置。

其他成员变量要对齐某个数字(对齐数,vs默认为8)的整数倍的地址处。

对齐数是成员大小和默认对齐数相比较小的哪一个。

结构体总大小为最大对齐数的整数倍。

如果嵌套结构体,那么最小对齐数就是最大的结构体整体的大小。

代码语言:javascript
代码运行次数:0
复制
class A
{
	char b;
	char c;
	int a;
	char d;
	char e;
	//12
};

int main()
{
	cout << sizeof(A) << endl;
}

如果没有成员变量,那么大小就是1,只是为了表明对象存在。

this指针

每个类名去访问类函数的时候,是用this来表明访问者,可以区分成员函数所调用的成员。

在编译器编译后,都会在类成员函数形参第一个位置提案加一个当前类类型的指针,叫做thsi

显示出来就是:"void A(Typecalss* const this,....);"

类的成员函数中华访问成员变量,本质都是通过this指针访问呢的,"this->_number"

C++规定不能再实参和形参位置写出this指针,但是可以在函数体内显示的使用this指针。

代码语言:javascript
代码运行次数:0
复制
#include<iostream>
using namespace std;
class A
{
public:
	char T()
	{
		this->b = 0;//显式调用
		b = 0;//隐式调用
		return b;//return this->b;
	}
	char b;
	//12
};

int main()
{
	A b;
	cout << b.T() << endl;

}

this 指针会因编译器不同而有不同的存储位置,可能是栈、寄存器或全局变量

类的默认成员函数

类有六个默认成员函数:

构造函数 析构函数 拷贝构造 赋值重载 运算符重载 const修饰对象重载

构造函数

函数名与类名相同

无返回值

对象实例化时系统会自动调用对应的构造函数

构造函数可以重载

如果没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数

可以用 类名=Default 强制生成构造函数

无参构造函数,全缺省构造函数,自动生成的构造函数,都是默认构造函数,这三个函数只能有一个存在,不能同时存在。无参构造函数和全缺省函数虽然构成函数重载,但是调用会有歧义,使用会报错。

构造函数下可以用初始化列表进行构造,初始化列表的格式就是以:开始 ,分割 然后成员变量后面跟括号表示赋给的值 :A(a),B(b)

每个成员变量只能在初始化列表内出现一次,可以认为是每个成员变量自定义初始化的地方

const成员变量没有默认构造的类类型变量,就必须在初始化列表进行初始化,不然会报错

C++11允许给成员变量在声明的地方给出一个缺省值,如果没有初始化列表就按照缺省值初始化

初始化列表按成员变量在类中声明的顺序进行初始化,和列表的顺序无关,建议列表顺序和声明顺序同步。

代码语言:javascript
代码运行次数:0
复制
class A
{
public:
    A():a(int()),b(int()){}
int a;
int b;
}
代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	/*
	A() = default;//强制生成
	
	A() :b(1){}//无参构成 初始化列表
	
	A(int a, int b){}//带参构造
	
	A(int a = 1, int b = 1) {}//全缺省构造
	*/
	char b;

};

析构函数

构系函数的目的时为了对对象本身的销毁,如果局部域是存在栈帧的,函数结束后也就释放了。

构系函数名是在类名前面加~

无参数无返回值

一个类只能有一个构系函数,如果没有显式定义,系统会自动生成默认的构系函数

对象生命周期结束时,系统会自动调用构系函数

和构造函数类似,我们不写编译器自动生成的构系函数对内置成员不做处理,自定义类型成员会调用它自己的析构函数

无论我们写不写析构函数,自定义类型成员都会调用它自己的析构,也就是自定义类型成员无论什么情况都会自动调用析构函数

如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的析构函数

一个局部域的多个对象,C++规定先定义后析构

代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	A():a(new char[100]),b(0)//用new申请了空间
	{

	}
	~A()//如果没写~A(),它也会自动调用delete把a申请的空间还回去
	{
		delete[] a;
	}
	char* a;
	char b;

};
class B
{
public:
	B()
	{
		
	}
	A a;//a自己会析构
};

拷贝构造函数

拷贝构造的第一个参数是对自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一种特殊的构造函数

拷贝构造函数是构造函数的一种重载

拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为在语法逻辑里面,传参是拷贝构造出一个函数,那么就会不断地拷贝构造,也就无穷递归调用,也就报错了

C++规定自定义类型进行拷贝行为必须先调用拷贝构造,使用这里自定义类型传参和传值返回都会调用拷贝构造函数完成

若没有显式定义拷贝构造,编译器会自动生成拷贝构造函数,自动生成的拷贝构造是浅拷贝(值拷贝),就是对内容进行一个字节一个字节的拷贝

如果是没有内置类型指向的申请资源空间的地址(或相似的玩意),编译器自动生成的拷贝构造可以完成浅拷贝,但是两个部分会指向同一个空间,在析构的时候会调用两次对同一个地址的析构,就会报错,这个时候浅拷贝就不好用了,要自己写。

传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝构造,如果返回对象是一个当前函数局部域的局部对象,函数体结束会被销毁,引用返回可以认为是返回了一个野指针,就会出现未知的错误。

代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	A():number(new int(int())),_number(int()){}//构造函数
	//A(A a)会报错 因为触发无穷递归了
	A(const A& a)//拷贝构造 //如果不写也会产生一个浅拷贝 会对
	{
		//浅拷贝干的事情
		/*
		number = a.number;
		_number = a._number;
		*/
		//自己写
		number = new int(*(a.number));
		_number = a._number;
	}
	A funtion() { return *this; }//返回的是对自己的拷贝构造后的值
	A& _funtion() { return *this; }//返回的是对自己的引用
	void p()
	{
		cout << number << ' ' << *number << ' '<< _number << endl;//这里看到地址不同 但是值相同
	}
private:
	int* number;
	int _number;
};

运算符重载

当运算符被用于类类型的对象时,C++允许我们通过运算符重载的形式指定新的含义。C++固定对类类型对象使用运算符时,必须转换成调用对应的运算符重载,若没有对应的重载就会编译错误。

运算符重载的格式:operator + 要定义的运算符 比如 “void operator()" 对 "()" 进行运算符重载,它也具有返回值,和普通函数类似。

如果是一元运算符就只有一个参数,两元运算符就需要两个参数,两元运算符左侧运算对象传参给第一个参数,右侧运算对象给第二个参数。

如果重载运算符函数是成员函数,则第一个运算对象默认传给隐式的this指针,因此在运算符重载作为成员函数的时候,参数比运算对象少一个

运算符重载后,优先级与结合行于对应的内置类型运算符保持一致

不能把语法中没有的符号创建运算符

.* :: sizeof ?: . 不能重载

重载操作符至少有一个类类型参数,不能通过运算符从在改变内置类型对象的含义,也就是不能改变人家原有的意思,如不可以写:int operator+(int left,int right);

运算符重载要选择性的重载,重载出的要有意义

重载++(--)运算符时,有前置和后置之分,它们的重载函数名都是++,C++谷底那个后置++重载要跟个int形参,和前置++区分,比如前置: operator++() 后置 operator++(int)

重载<<与>>函数时,需要重载为全局函数,因为重载为成员函数,this会抢第一个形参的位置,但是第一个形参应该是左侧运算符对象,如果在类中重载就需要这样写 <<cout才能调用<<的重载,不仅难于理解可读性也大大降低。

取自己写的代码:

代码语言:javascript
代码运行次数:0
复制
ostream& my_list::operator<<(ostream& _cout, const my_list::string& s)//对list的<<重载
{
	for (size_t i = 0; i < s.size(); ++i)
 
	{
		_cout << s[i];
	}
	return _cout;
}
istream& my_list::operator>>(istream& _cin, my_list::string& s)
{
	s._size = s._capacity = 0;
	char c;
	while (1)
	{
		c = std::getchar();
		if (c == ' '|| c=='\n')
		{
			return _cin;
		}
		s += c;
	}
	return _cin;
}

//解释一下 ostream与istream是输出输入流 记住即可
//在类中声明需要用frined 朋友函数

friend ostream& operator<<(ostream& _cout, const my_list::string& s);
friend istream& operator>>(istream& _cin, my_list::string& s);

//可以调用类里面的东西

其它运算符重载

代码语言:javascript
代码运行次数:0
复制
    bool operator<(const string& s);
    bool operator<=(const string& s);
    bool operator>(const string& s);
    bool operator>=(const string& s);
    bool operator==(const string& s);
    bool operator!=(const string& s);
代码语言:javascript
代码运行次数:0
复制
    Self& operator++();
    Self& operator++(int);
赋值运算符

对于赋值运算符,要求必须重载为成员函数,而且建议形参用const修饰。

有返回值,是当前类类型引用,可以提高效率和连写运算符

没写的时候,编译器也会自动生成,默认的赋值运算符和默认的拷贝构造类似,是浅拷贝,要考虑是否自己写

代码语言:javascript
代码运行次数:0
复制
 list(const list<T>& l)//拷贝构造
 {
     CreateHead();
     for (auto it : l)
     {
         push_back(it);
     }
 }
 list<T>& operator=(const list<T>& l)//拷贝运算符重载
 {
     list(l);//建议把拷贝运算符重载给拷贝构造干反正干的事情一样
 }

类型转换

C++支持内置类型隐式转换为类类型对象,需要有相关内置类型的构造函数。

在构造函数前加explicit就不能隐式转换了

类类型对象之间也可以隐式转换,需要相应的构造函数支持。

代码语言:javascript
代码运行次数:0
复制
class A
{
public :
	// 构造函数explicit就不再⽀持隐式类型转换 explicit A(int a1)
	A(int a1): _a1(a1){}//初始化列表 对_al进行列表初始化 如果没有写会用1这个缺省值
	A(int a1, int a2):_a1(a1), _a2(a2){}

	int _a1 = 1;//这个是缺省值
	int _a2 = 2;//缺省值
};
class B
{
	public :
	B(const A& a)
		: _b(a._a1)
	{}
private:
	int _b = 0;
};
int main()
{
	// 编译器遇到连续构造+拷⻉构造->优化为直接构造
	A a = 1;//对1隐式转换
	A c = { 2,2 };//对2,2整体隐式类型转换
	B b = c;
	//他俩先拷贝构造成a隐式类型转换成a
	B e(1);
	B r = {1};
	//
	const B& rb = c;
	return 0;
}

static成员(静态成员)

用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在此类外进行初始化 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区

用static修的的成员函数,是静态成员换上,没有this指针

非静态的成员函数,可以访问任意的静态成员变量和静态成员函数

可以用类名::静态成员 或者 对象.静态成员 来访问静态成员函数和静态成员

静态成员也是类的成员,受public、protected、private访问限定符的限制

静态成员变量不能在声明位置给缺省值初始化,因为缺省值是构造函数初始化列表用的,静态成员不属于某个对象里,不走给构造函数初始化列表

代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	int numb()
	{
		return number;//普通的可以访问静态
	}

	static int num()//静态只能访问静态
	{
		return number;
	}

	static int number;//类内声明
};

int A::number = 1;//类外定义

int main()
{
	A a;
	a.number++;//之间访问
	A b;
	cout << b.num() << b.numb() << endl;
	return 0;
}

友元

友元提供了一种突破类访问限定符封装的方法,分为友元函数和友元类,在函数声明或者类声明前加friend就是对friend开放权限。

外部友元函数可以访问类的私有和保护成员

友元函数可以在类定义的任何地方声明

一个函数可以是很多个函数的友元函数

友元函数的成员函数都可以说另一个类的友元函数,都可以访问另一个类中的私有和保护成员

友元关系是单向的 我的朋友是你 你的朋友不是我 我对你开放权限 你却不对我开放权限 就像是我喜欢你但是你不喜欢我,即使我对你开放我的一切,你不对我开放,我就还是无法访问你的内心

友元关系不能传递,A是B友元 C是B友元 C不是A的友元

代码语言:javascript
代码运行次数:0
复制
class C
{
public:
	void cc(A& a)
	{
		a._A = 0;//类友元访问
	}
};
class A
{
	friend void B(A& a);//友元函数
	friend class C;//类友元
private:
	int _A;
};
void B(A& a)
{
	a._A = 0;//友元函数访问
}

内部类

一个类被定义在另一个类内部,就是内部类,是一个独立的类,和定义在全局相比,只是收到了外部类类域的限制,外部类定义的对象中不包含内部类。

内部类默认是外部类的友元类

内部类是一种封装,如果把内部类放到私有或者保护的位置,那么内部类就是外部类的专有类了

代码语言:javascript
代码运行次数:0
复制
class A
{
	friend void B(A& a);//友元函数
private:
	class C//A的专属类
	{
	public:
		void cc(A& a)
		{
			a._A = 0;//类友元访问
		}
	};
	int _A;
};
void B(A& a)
{
	a._A = 0;//友元函数访问
}

匿名对象

用 类型或实参定义出来的对象就是匿名对象 就是没名字,用名字定义出来的就是有名对象

生命周期只有使用它的当前这一行

代码语言:javascript
代码运行次数:0
复制
class A
{
public:
	int c()
	{
		return 1;
	}
};

int main()
{
	cout << A().c();
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类的定义
    • 定义格式
    • 访问限定符
    • 类域
  • 实例化
    • 概念
    • 对象大小
      • 对齐规则
  • this指针
  • 类的默认成员函数
    • 构造函数
    • 析构函数
    • 拷贝构造函数
    • 运算符重载
      • 赋值运算符
  • 类型转换
  • static成员(静态成员)
  • 友元
  • 内部类
  • 匿名对象
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档