🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️
本博客致力于分享知识,欢迎大家共同学习和交流。
什么是类?
C语言中一般是由数据和算法组成,数据和算法彼此独立,关联性不强,在C++中将相互关联数据和算法封装起来,形成结构体或类,无论类还是结构体都是一个抽象的概念,只有定义类的变量时,数据才会真实存在,这个变量我们称之为对象,C++程序过程中,尽量避免单独的数据和算法,而是由一个个类对象组成的,这就是面向对象。
类﴾class﴿:完成某一功能的数据和算法的集合,是一个抽象的概念。可以将其视为一个模板对象:类的一个实例,具体的概念,是真正存在于内存中的。
C++中类和结构体的区别(主要的两点):
- 类成员属性、方法默认是私有的﴾private﴿,而结构体默认是公有的﴾public﴿。
- 当从基类、结构体中继承时,类的默认继承方式是私有的﴾private﴿,而结构体是公有的﴾public﴿。
定义类的关键字 class,类名一般以大写的C开头,成员属性一般以m_开头。
class CPeople {
string m_strName; //类成员变量(类成员属性)一般以m_ 开头
bool m_bSex;
int m_nAge;
void play() { //成员函数 成员方法
cout << m_strName << "is playing" << endl;
}
};
类访问修饰符(按权限高低排序):public protected private
#include<iostream>
#include<string>
using namespace std;
//class关键字,用来定义类。类名一般以大写的C开头
class CPeople {
//访问修饰符:public protected private
public: //所有地方都可以访问修改
string m_strName; //类成员变量(类成员属性)一般以m_ 开头
private: //只在此类中可以访问
bool m_bSex;
protected: //此类及其子类可以访问
int m_nAge;
public:
void play() { //成员函数 成员方法
cout << m_strName << "is playing" << endl;
}
void show() {
cout << "name=" << m_strName << " ,sex=" << m_bSex << " ,age=" << m_nAge << endl;
}
void setAge(int age) {
m_nAge=age;
}
int getAge() {
return m_nAge;
}
void setSex(bool sex) {
m_bSex = sex;
}
int getSex() {
return m_bSex;
}
};
int main() {
peo.show(); //直接打印会显示乱码
peo.setAge(20);//由于访问权限符修饰,可以通过set函数设定年龄
cout << "年龄修改为 "<<peo.getAge() << endl;
peo.show();
peo.setSex(1);
cout << "性别修改为 " << peo.getSex() << endl;
peo.show();
return 0;
}
构造函数和析构函数
构造函数并不需要我们手动调用,在定义对象的时候会自动调用,这个默认的无参构造是编译器给提供的,函数体代码为空,所以在定义对象时虽然调用了,但并没有给成员初始化。所以需要手动重构构造函数。一个类中的构造函数允许存在多个,他们是函数重载的关系,重构的构造函数可以指定参数来符合我们需要的初始化过程。注意:只要重构了任何的构造函数,编译器将不会再提供那个默认的无参构造了。定义多个对象可能会执行不同的构造,这就要看在定义对象时如何指定参数了,会根据参数的类型、数量自动匹配对应的构造,但一个对象最终只能执行其中一个构造。
构造函数, 函数名 为类名,参数任意,没有返回类型(返回值不是void,而是真的没有)在定义对象时,编译器自动调用构造函数,完成初始化若未定义构造函数,类中自带一个缺省的构造函数
//缺省的构造函数
类名()
{
}
无参数的构造函数:
CPeople() {
m_strName = "李四";
m_bSex = true;
m_nAge = 25;
}
有参的构造函数:
CPeople(string name) {
m_strName = name;
m_bSex = false;
m_nAge = 30;
m_cNote = new char[30]{"我是一个学生"};
}
CPeople(int age) {
m_strName = "张三";
m_bSex = false;
m_nAge = age;
m_cNote = new char[30] {"我是一个好的学生"};
}
//构造函数可以进行重构
new申请对象内存空间时,编译器自动调用构造函数:
CPeople* ppeo = new CPeople("老王");//new在申请内存空间时自动调用构造函数
/* ppeo->show();
delete ppeo;
ppeo = nullptr;*/
//根据参数类型匹配重构的函数
析构函数:
析构函数:与构造函数相对应的析构函数,其作用是用来回收在类中申请的额外的空间。空类中存在一个默认的析构函数,函数名为~类名,无返回值,无参数。
~CPeople() { //析构函数无法释放对象的空间,而是释放对象额外申请的堆区空间
if (m_cNote)
delete[]m_cNote;
m_cNote = nullptr;
}
struct和class的区别
C++中:
(1)struct和class关键字都可以定义类,但是struct默认的访问限定是public,class默认的访问限定是private。
(2)struct默认是公有继承(public),class默认是私有继承(private)
(3)当struct和class都定义了构造函数,就不能使用大括号对其进行初始化。
(4)若没有定义构造函数,struct可以使用{}进行初始化,而只有当class的所有数据成员及函数为public时,可以使用{}进行初始化。
(5)struct更合适看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。
(6)在模板中,类型参数前面可以使用class或typename,如果使用struct,则含义不同,struct后面跟的是"non-type template parameter“,而class或typename后面跟的是类型参数。
类一般成员
若定义了一个类,类中无成员属性和函数,则此类占用一个字节。用于占位(标识)此类存在
#include<iostream>
class CTest {
};
int main() {
std::cout << sizeof(CTest) << endl; //当类中没有定义成员和方法时,类占用1个字节,这一个字节用于标识,占位
return 0;
}
类成员属性:属于对象,在定义对象的时候就存在。定义多个对象,会存在多份的成员,彼此独立,互不干扰
#include<iostream>
class CTest {
public:
//类成员属性:
int m_a;
int main() {
CTest tst1;
cout << sizeof(CTest) <<" "<<sizeof(tst1)<<endl;//4 4
}
类成员函数:属于类,在编译期就存在。一个类中只存在一份,被多个对象所共享
#include<iostream>
/*
成员函数:属于类的,(可以理解为全局函数)在编译期就存在,一个类中只存在一份,被多个对象所共享
*/
class CTest {
public:
int m_a;
void fun() {
cout << "CTest::fun" << endl;
}
int main() {
CTest tst1;
CTest tst2;
cout << sizeof(CTest) <<" "<<sizeof(tst1)<<endl;
}
这里我们可以看到,当类中有成员函数时,我们在定义对象后,对象所占内存空间不变。我们可以将其理解为,类中的函数存放在代码区。
this指针
既然多个对象共享一个类成员函数,那么我们在使用不同对象调用类函数时,是否会混淆呢?
这里需要引入C++中的this指针。this指针,C++中的关键字。非静态类成员函数中隐藏的第一个参数,由编译器默认添加。指向了调用该函数的对象,在函数中调用其他的成员函数,都是通过this指针默认调用的,但是this可以省略不写(编译器默认加上)
#include<iostream>
using namespace std;
/*
在 C++ 中,this 指针是一个特殊的指针,它指向当前对象的实例。
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。
*/
class CTest {
public:
int m_a;
void fun() {
cout << "CTest::fun" << endl;
}
CTest() {
m_a = 1;
}
~CTest() {
cout << "~CTest" << endl;
}
//this指针,C++中的关键字。非静态类成员中隐藏的第一个参数,由编译器默认添加
//指向了调用该函数的对象,在函数中使用其他的类成员,都是通过this指针默认调用的,但是this可以省略不写(编译器默认加上)
//作用:连接对象和类成员之间的“桥梁”
void fun2(/*CTest * const this */) {
cout << "m_a=" << m_a << endl;
//this->m_a=10; //通过this调用
}
void address() {
cout << "this=" << this << endl;
}
};
void fun() {
cout << "fun" << endl;
}
//可以将该函数参数看作this指针的本质,类中的函数参数this,是调用该函数的对象的地址
//当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为 this 指针。
void fun2(CTest * const pthis) {
cout << "m_a=" << pthis->m_a << endl;
}
int main() {
CTest tst1;
CTest tst2;
tst1.fun2(/*&tst1*/);
tst2.m_a = 120;
tst2.fun2(/*&tst2*/);
cout << &tst1 << endl;
tst1.address();
return 0;
}
对象在调用函数时,编译器会将对象地址作为参数传入。通过 &对象 和 直接输出类中的this,我们可以看出二者相同,因此this指针可以避免不同对象调用函数所造成的混淆。
静态成员
静态成员需要使用关键字 static 修饰。
静态成员包括静态成员属性、静态成员函数。一个属性一旦设置为静态,那么整个程序中就独其一份。
静态属性会在代码运行前的编译阶段就生成并保持存在全局区。静态属性和静态成员函数都不算在类内,都是保存在别的区域(全局区)。
静态属性必须要有一个初始值,可以在类内声明,类外初始化。
static int m_a;
static void show()
静态成员属性:与成员函数一样,属于类的,在编译期就存在,只存在一份,被多个对象所共享
#include<iostream>
using namespace std;
//静态成员属性:属于类的,在编译期就存在,只存在一份,被多个对象所共享
class CTest {
public:
int m_a;
static int m_b;
CTest() {
m_a = 1;
m_b = 5;//在类中可以给静态成员赋值,但是定义及初始化在类外
}
};
int CTest::m_b = 2;//静态成员在类外定义以及初始化,static可以省略,类名和作用域不能省略
int main() {
//CTest tst1;
cout << "sizeof(tst1)="<< sizeof(tst1)<<endl;//输出结果:sizeof(tst1)=4
//静态成员在静态区,对象在栈区,其内存计算只包含栈区的部分
cout << "m_b=" <<tst1.m_b << endl;//m_b=2
//无法解析的外部符号 "public: static int CTest::m_b" (?m_b@CTest@@2HA):有声明,无定义
cout << "m_b=" << CTest::m_b << endl;//由此可以看出,静态成员是否通过对象都可以调用
CTest tst2;
tst2.m_b++;
cout << "m_b=" << tst2.m_b << endl;//m_b=3
cout << "m_b=" << tst1.m_b << endl;//m_b=3
//由此可以看出,多个对象之间共享一个静态成员
return 0;
}
静态函数:属于类的,在编译期就存在,只存在一份,被多个对象所共享
类的静态成员函数是整个类共有的,并非属于类的某个对象。所以,静态成员函数没有this指针,因此,只能访问类的静态数据和静态成员函数。
#include<iostream>
using namespace std;
//静态函数:属于类的,在编译期就存在,只存在一份,被多个对象所共享
/*
静态成员函数与普通成员函数的区别:
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
*/
class CTest {
public:
int m_a;
static int m_b;
CTest() {
m_a = 1;
m_b = 5;//在类中可以给静态成员赋值,但是定义及初始化在类外
}
void fun() {
cout << m_a << endl;
cout << m_b << endl;
}
//静态函数:没有隐藏参数this指针,
// 因此不能在静态函数中使用一般成员属性,只能用其他静态成员(静态成员属性,其他的静态成员函数)
static void funStatic() {
//cout << m_a << endl; //error:this->m_a
cout << "funStatic:" << m_b << endl;
//fun(); //error
funStatic2();
}
static void funStatic2() {
//cout << m_a << endl; //error:this->m_a
m_b = 5;
cout << "funStatic2:" << m_b << endl;
}
};
int CTest::m_b = 2;//静态成员在类外定义以及初始化,static可以省略,类名和作用域不能省略
int main() {
CTest::funStatic();//是否通过类都可以使用
return 0;
}
在静态函数参数中,没有this指针,因此静态函数中不能调用类本身的成员和函数,只能访问其他静态成员。
静态成员属性与函数是否通过类都可以使用
如这种情况,对象所占空间内存大小就是4。
#include <iostream>
using namespace std;
class Test
{
public:
static int m_a;
static int m_b;
int m_c;
~Test(){};
};
int Test::m_a = 1;
int main()
{
Test tst;
cout << sizeof(tst) << endl;
return 0;
}
之后不论给里面放了多少静态成员属性和多少成员函数,对象所占空间大小都是4,因为这些内容不会和对象存储在一起。
类内的成员函数和非成员函数均放在代码区,只有实例化对象的时候才会在栈区内为其分配空间。
类的静态成员属性和静态函数在编写定义类的代码时就已经在全局区为其分配好内存了,因此他是属于类的
理解编译期和运行期:
编译期是指把源程序交给编译器编译、生成的过程,最终得到可执行文件。运行期是指将可执行文件交给操作系统执行、直到程序退出,执行的目的是为了实现程序的功能。类是编译期的概念,包括成员的访问控制和作用域。对象是运行期的概念,包括定义类的实例、引用、指针等使用其类成员。
常量成员
初始化参数列表,写在构造函数之后,用:开始,给变量初始化使用() {} ,是构造函数的一部分
类中的常量成员,需要通过初始化参数列表来对其进行初始化
若构造函数未对其进行初始化,则编译不会通过
初始化参数的顺序:按照 类成员属性定义的先后顺序,和 写在初始化参数列表中的顺序无关
class CTest {
public:
int a;
const int b;
//初始化参数的顺序:按照 类成员属性定义的先后顺序,和 写在初始化参数列表中的顺序无关
CTest(int x) :b(x),a(b) {
}
};
int main(){
cout << "tst1.a=" << tst1.a << " tst1.b="<< tst1.b<<endl;
CTest tst2(8); //按照成员定义先后顺序,先对a进行初始化,将b赋值给了a,因此会出现乱码值
// tst2.a=-858993460 tst2.b=8
}
#include<iostream>
using namespace std;
class CTest {
public:
int a;
const int b;
CTest() :b(5){ //error C2789: “CTest::b”: 必须初始化常量限定类型的对象
a = 1;
//b = 1; //error:表达式必须是可修改的左值
}
//初始化参数的顺序:按照 类成员属性定义的先后顺序,和 写在初始化参数列表中的顺序无关
CTest(int x) :b(a),a(x) {
}
};
int main() {
CTest tst1;
// tst2.a = 8 tst2.b = 8
cout << "tst2.a=" << tst2.a << " tst2.b=" << tst2.b << endl;
return 0;
}
常成员函数
常函数:类中的成员函数参数列表后面有const修饰时,称之为常函数,其主要作用是为了能够保护类中的成员变量,其特性是:不能修改类中的非静态成员,因为const修饰this指针变为const类* const this,也就是不能执行 this‐>变量=val 操作,但是仍然可以查看成员变量。对于静态成员属性不但能查看,也能对其修改,因为静态成员可不通过this 去使用。在常函数中可以查看普通的变量、常量、静态变量等,也可以调用其他常函数,但是却不能使用普通的成员函数,因为其this指针的类型并不相同,CTest* const this = const CTest* constthis 这将是一个非法的操作
常成员函数无法修改非静态成员属性,可以修改static修饰的静态成员属性(static修饰的成员不通过对象也可以使用)
void funConst(/*const CTest * const this*/) const {
cout << a << " " << b << endl;
//a = 5;//error:常函数无法修改非静态成员属性
c = 8;//静态成员属性,可以修改
}
类中常函数,一般函数,静态函数的相互调用
常成员函数无法调用一般函数,因为函数安全性降低了:const CTest * const this ---> CTest * const this 。但是反过来可以, CTest * const this ---> const CTest * const this ,安全性提升了。但是常成员函数可以调用静态函数
void funConst2(/*const CTest * const this*/) const {
//funCommon(/*CTest * const this*/);//error:安全性降低,无法调用
funStatic();//可以调用
}
void funCommon() {
funConst2();
funStatic();//可以调用
}
static void funStatic() {
//funCommon();//无this指针,无法调用
//funConst2();//无this指针,无法调用
}
类中常函数,一般函数,静态函数的重载
函数重载回顾
- 函数重载的本质为相互独立的不同函数
- C++中通过函数名和函数参数确定函数调用
- 无法直接通过函数名得到重载函数的入口地址
- 函数重载必然发生在同一个作用域中
类中的重载
- 构造函数的重载
- 普通成员函数的重载
- 静态成员函数的重载
重载函数的本质为多个不同的函数,函数名和参数列表是唯一的标识,函数重载必然发生在同一个作用域中
常函数和一般函数可以重载,传入变量时调用一般函数,传入常量时调用常函数。静态函数不可与之进行重载,当我们通过对象调用函数时,无法判断调用的是静态函数还是一般函数
//常函数,一般函数,静态函数的重载
void fun() { //定义一般对象时,调用此函数
cout << "fun()" << endl;
}
void fun() const { //定义常量对象时,调用此函数
cout << "fun() const" << endl;
}
//当成员函数在类中具有相同的名称和相同的参数列表时,则不能重载它们。
//static void fun(){} //error C2686: 不能重载具有相同参数类型的静态和非静态成员函数