C++中的类

一,类的定义

cpp 复制代码
class classname
{
    //类体由成员函数和成员变量组成

};

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。

类的两种定义方式:

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
  2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::

二,类的访问限定符

  1. 访问限定符有三种分别为 public private protected
  2. public:类内类外都可以直接访问
  3. private:只有类内成员可以访问,类外成员不可以访问
  4. protected:只有类内成员可以访问,类外成员不可以访问
  5. C++中如果没有明确声明,类中的所有成员和函数都是私有的,即private
  6. 访问限定符的作用范围为从该限定符开始一直到下一个限定符,或者类的结束

三,类中的this指针

this指针的相关特性

  1. this指针只作用于类的内部
  2. 类中的非静态成员函数其参数列表中都有一个this指针,this指针的传递由编译器帮组我们完成,不需要用户主动传参
  3. this指针指向当前对象,被const所修饰,不可被修改
  4. 当我们在类的内部调用成员函数或者成员变量时(非静态),是以this->(成员函数或者成员变量),但是this->一般可以省略

关于this指针的小练习

cpp 复制代码
// 1.下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}
// 1.下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}
  1. 第一个正常运行,因为void Print()内部没有调用成员函数和成员变量,所以就没有用到this指针,所以没有报错能正常运行
  2. 第二个报错,因为void Print()内部用到了成员变量,也就用到了成员变量,所以会报错

四,类的6个默认成员函数

构造函数

  1. 函数名与类名相同,创建对象时由编译器自动调用,没有返回值
  2. 主要任务不是开辟空间创建对象,而是用于初始化成员函数
  3. 构造函数可以重载
  4. 如果我们没有显示的在类中写构造函数,编译器会自动的帮我们生成一个无参的默认构造函数,该默认构造函数在构建对象时会调用成员变量的构造函数(只对自定义类型生效)
  5. :C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在 类中声明时可以给默认值。

析构函数

  1. 对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
  2. 析构函数名是在类名前加上字符 ~,无参数无返回值类型
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
  5. 如果我们没有显示的在类中写析构函数,编译器会自动的帮我们生成一个析构函数数,该析构函数在对象销毁时会调用成员变量的析构函数

拷贝构造函数

  1. 拷贝构造函数是构造函数的一个重载形式,函数名与类名相同,没有返回值
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
  4. 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
  5. 拷贝构造函数典型调用场景:
  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

参数不是类类型对象的引用错误示例如下:

cpp 复制代码
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(const Date& d)   // 正确写法
	Date(const Date& d)
		// 错误写法:编译报错,会引发无穷递归
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

错误原因如下:

直接使用浅拷贝错误示例如下:

cpp 复制代码
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
			_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

运行结果如下:

原因:程序退出时s2,s1都需要调用析构函数对内存进行释放,s2先调用析构函数对所申请的空间进行释放,这是s1并不知道内存已经被释放了,又因为浅拷贝指针指向的是同一块空间,对已经释放过的空间再次进行释放,必然会报错

赋值运算符重载
运算符重载
  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
  5. . * :: sizeof ?: . 注意以上5个运算符不能重载
赋值运算符重载
  1. 参数类型:const T&,传递引用可以提高传参效率
  2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  3. 返回*this :要复合连续赋值的含义
  4. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
  5. 内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。
  6. 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必 须要实现,原因与上述拷贝构造相同
  7. 赋值运算符只能重载成类的成员函数不能重载成全局函数,示例如下:
cpp 复制代码
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
 }
 // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
 Date& operator=(Date& left, const Date& right)
 {
	 if (&left != &right)
	 {
		 left._year = right._year;
		 left._month = right._month;
		 left._day = right._day;
	 }
	 return left;
	 // 编译失败:
	// error C2801: "operator ="必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值 运算符重载只能是类的成员函数。

前置++和后置++重载
  1. 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载

C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器 自动传递

前置++如下:

cpp 复制代码
Date& operator++()
 {
 _day += 1;
 return *this;
 }

后置++如下:

cpp 复制代码
Date operator++(int)
 {
 Date temp(*this);
 _day += 1;
 return temp;
 }

五,const成员

  1. 将const修饰的"成员函数"称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改(上文中提到的this被const修饰,是指针自身不能被修改)。

六,再谈构造函数

  1. 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值。
  2. 所以我们引进一个新的概念初始化列表

初始化列表

  1. 以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式,表现形式如下:
cpp 复制代码
class Date
{
public:
    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
        
private:
    int _year;
    int _month;
    int _day;
};

【注意】

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
  4. 类中包含以下成员,必须放在初始化列表位置进行初始化
  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

explicit关键字

  1. 构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。
  2. 单参构造函数,没有使用explicit修饰,具有类型转换作用
  3. explicit修饰构造函数,禁止类型转换

七,static成员

static成员概念

  1. 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

计算程序中创建出了多少个类对象

cpp 复制代码
class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() { --_scount; }
	static int GetACount() { return _scount; }
private:
	static int _scount;
};
int A::_scount = 0;

void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}

int main()
{
	TestA();
	return 0;
}

答案:是一个

static成员性质

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  6. 静态成员函数不可以调用非静态成员函数
  7. 非静态成员函数可以调用类的静态成员函数

八,友元

友元函数

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用

友元分为:友元函数和友元类

  1. 友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。
  2. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  3. 友元函数不能用const修饰
  4. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  5. 一个函数可以是多个类的友元函数
  6. 友元函数的调用与普通函数的调用原理相同

友元类

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
  2. 友元关系是单向的,不具有交换性(A为B的友元类,A可以访问B中的所有私有成员函数和变量,但是B不能访问A中的私有成员函数和成员)
  3. 友元关系不能传递(如果C是B的友元, B是A的友元,则不能说明C是A的友元)
  4. 友元关系不能继承,在继承位置再给大家详细介绍

九,内部类

  1. 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限
  2. 内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元
  3. 内部类可以定义在外部类的public、protected、private都是可以的
  4. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
  5. sizeof(外部类)=外部类,和内部类没有任何关系

十,匿名对象

  1. 假设有一个类的类名为A,定义对象时不能 A+对象名+(),这样编译器无法识别是函数声明还是定义对象
  2. 可以A+()定义对象,作用域为该行
相关推荐
小_太_阳6 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
向宇it7 分钟前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
lxyzcm17 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿35 分钟前
C/C++基础错题归纳
c++
古希腊掌管学习的神38 分钟前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca39 分钟前
洛谷 P1706 全排列问题 C语言
算法
古希腊掌管学习的神43 分钟前
[LeetCode-Python版]相向双指针——611. 有效三角形的个数
开发语言·python·leetcode
赵钰老师44 分钟前
【R语言遥感技术】“R+遥感”的水环境综合评价方法
开发语言·数据分析·r语言
浊酒南街44 分钟前
决策树(理论知识1)
算法·决策树·机器学习
雨中rain1 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++