抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

首先的话解释一下什么是类吧

1
2
3
4
5
class student {
string name;
int age;
}; // ← 这里必须有分号!

就是带class的关键字,里面的姓名和年龄就是类的成员数据,也叫做属性,其实和C的结构体是差不多的

类中的publick和private

  • publick里面的成员是可以在类的外部进行访问的
  • private里面的成员是不用在类的外部访问的

先看如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <string>
#include <iostream>
using namespace std;

class student {
public:
string name = "Alice";
private:
int age = 18;
}; // ← 这里必须有分号!


int main(){
student aa;
cout << aa.name << endl; //输出Alice
// cout << aa.age << endl; 这个是会报错的,因为私有成员变量是不允许在类的外部直接访问的
return 0;
}

再看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
void print_name();
private:
void print_age();
};


void student :: print_name(){
print_age(); //通过公有的来调用私有的
cout << "公有" << endl;
}

void student :: print_age(){
cout << "私有" << endl;
}

int main(){
student aa;
aa.print_name();
// aa.print_age(); //这个是会报错的,提示student类找不到这个方法,私有的是不能直接被调用的

return 0;
}

这边其实就是private对类外部隐藏类内部的工作机制,只能在类里面看到,也就是封装性

但不是说私有方法就无法被调用了,可以通过公有方法来调用

其实上面写法都是不正规的,

  • image-20250629131553036

重载

成员函数的重载是指一个类中可以有重名名的,但是参数不同的函数

看如下代码

1
2
3
4
5
6
7
class student {
string name = "Alice";
int age = 18;

bool set(int a);
bool set(string a);
};

编译器是可以区分这两个函数的,根据传入的参数类型而选择调用哪个函数

构造函数

  • 构造函数就是当你创建类的对象的时候会自动调用
  • 构造函数的名字必须和类的名字相同
  • 不需要声明是什么类型,也就是没有返回值,连void也不能写
1
2
3
4
5
6
7
8
9
10
11
12
class student {
string name = "Alice";
int age = 18;

// bool set(int a);
// bool set(string a);

student(){
name = "Bob";
age = 16;
}
};

这样就省去了手动初始化的过程,创建对象的时候就能直接初始化

也可以在类外定义构造函数,格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
class student {
string name = "Alice";
int age = 18;
student();
// bool set(int a);
// bool set(string a);
};


student :: student(){ //必须加上 类名 :: 类名()
name = "Bob";
age = 16;
}

有的是类内声明构造函数,类外实现

1
2
3
4
5
6
7
8
9
10
11
12
class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
string name = "Alice";
int age = 18;
student();
};


student :: student(){ //必须加上 类名 :: 类名()
name = "Bob";
age = 16;
}
  • 另外要注意,类外函数写实现之前,类内必须有声明,但如果你没有写实现,也没有写声明,会隐式生成

  • 只要你声明了函数,必须写出具体实现,不然编译器会报错

当然构造函数也是支持重载的,会根据实例化的情况决定调用哪个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
string name = "Alice";
int age = 18;
student();
student(int a, int b);
};

student :: student(){
name = "Bob1";
}

student :: student(int a, int b){ //必须加上 类名 :: 类名()
name = "Bob2";
}

int main(){
student aa;
cout << aa.name << endl; //Bob1
student bb(5, 3);
cout << bb.name << endl; //Bob2
return 0;
}
  • 如果类中没有定义手动定义构造函数,编译器会自己生成一个无参数的构造函数
  • 但如果你定义了构造函数,就不会生成

具体可以看如下的代码

1
2
3
4
5
6
7
class A {
// 什么都不写,编译器自动生成 A()
};

int main() {
A a; // ✅ 可以使用默认构造函数
}
1
2
3
4
5
6
7
8
class A {
public:
A(int x) {} // ✅ 你写了带参构造函数
};

int main() {
A a; // ❌ 错误!没有 A() 无参构造函数了
}

编译器不会帮你生成 A(),因为你自己写了构造函数,它认为你要自定义构造方式。

析构函数

析构函数主要是销毁对象,释放内存数据

  • 但是只适用于动态对象(new studeng,也就是堆对象),使用delete删除,局部对象(student aa,也就是栈对象)是不行的

  • 而局部对象(非new创建)是在生命周期结束后自动调用析构函数

看如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
string name = "Alice";
int age = 18;
student();
~student();
};

student :: student(){
name = "Bob1";
}

student :: ~student(){
cout << "删除完成" << endl;
}


int main(){
student *p = new student();
delete p; //输出删除完成
return 0;
}

释放内存实际上是delete的作用

调用delete后

  • 第一步:调用指针所指对象的 析构函数

    • 这是你可以自定义的部分,比如打印“删除完成”。
  • 第二步:调用 内存释放函数(operator delete),释放对象本体内存(从堆上释放)

  • 这个步骤由系统底层(runtime)完成。

所以比如说你直接调用析构函数是不能起到释放内存资源的作用的

而局部对象(非new创建)是在生命周期结束后自动调用析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

class student {
public:
student() { cout << "构造函数被调用" << endl; }
~student() { cout << "析构函数被调用" << endl; }
};

int main() {
cout << "进入 main()" << endl;
student s; // 局部对象,作用域是 main 函数体
cout << "即将结束 main()" << endl;
return 0;
}

输出

1
2
3
4
进入 main()
构造函数被调用
即将结束 main()
析构函数被调用

扩展知识点

  • 静态分配都是在栈上(大小只有几MB,分配速度比较快),会自动释放内存

    • 例如int arr[10]这种就是大小在编译的时候就确定的,
  • 动态分配都是在堆上(堆空间比栈大很多,几百MB到几GB),必须手动释放内存

    • 手动释放:delete[] arrfree(arr),前者是C++风格,后者是C的风格
    • 大小是在运行的时候实现的

还有一种是例如static int a = 5;或者是字符串常量,这种既不放在堆,也不放在栈,而是放在静态/全局数据段

常成员函数const

使用const修饰的函数实现只允许读数据,不允许修改数据,主要是为了防止误操作

看如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
string name = "Alice";
int age = 18;
// student();
// ~student();
void read() const;
};


void student :: read() const{ //类外实现的时候,也必须加上const
cout << name << endl;
// name = "Bob"; //这个是会报错的
}


int main(){
student *p = new student();
p -> read();
delete p; //输出删除完成
return 0;
}

输出

1
Alice

静态成员static

如果在类中定义定义了静态成员变量,那么他在类中是通用的,也就是不管你实例化了多少个对象,所有对象对应的静态变量是公用的

  • 注意类内只能声明,然后类外才能初始化
  • 初始化的时候不需要带static
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
string name = "Alice";
int age = 18;
student();
// ~student();
static int cnt; //类中只能声明,不允许初始化静态变量

};

int student :: cnt = 0; //类内声明,类外定义

student :: student(){
cnt += 1;
}


int main(){
student *p = new student();

student aa;
student bb;

cout << aa.cnt << endl; //输出为3
cout << bb.cnt << endl; //输出为3
cout << student :: cnt << endl; //输出为3
delete p; //输出删除完成
return 0;
}

类的派生继承

  • 继承指的是 一个类从另一个类获得属性和方法的机制

  • 派生类”就是通过继承而来的类,即“子类”的正式说法。

其中父类也叫基类、超类

子类也叫派生类

  • 子类是可以继承父类中public的属性和方法

具体看如下的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name();
private:
string name = "lin";
void print_age();
};


void student :: print_name(){
print_age();
cout << "公有" << endl;
}

void student :: print_age(){
cout << "私有" << endl;
}



class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来,public表示公有继承,只继承student公有的部分
public:
string course; //课程
};

class postgraduate : public student { //研究生
public:
string research; //研究方向
};



int main(){
undergraduate aa;
cout << aa.age << endl;
// cout << aa.name << endl; //私有的一样不能继承

return 0;
}
  • 类可以对继承来的父类方法进行重写,这是运行时多态的体现

  • 子类没有自己的构造函数时,会调用父类的构造函数(只能调用无参数的构造函数,带参数的是不行的)

  • 当然子类也可以有自己的构造函数,构造函数同样可以无参数,也可以带参数

  • 子类并不是继承了父类的构造函数

  • 子类实例化对象的时候,是先调用父类的构造函数,再调用子类的构造函数

一样看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name();
student(){
age = 8;
}

};


class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
string course; //课程
undergraduate(){
cout << age << endl;
age = 9;
}

};


int main(){
undergraduate aa;
cout << aa.age << endl;
return 0;
}

输出

1
2
8
9

再看如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name();
student(int a){
age = 8;
}

};

class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
string course; //课程
undergraduate(int a) : student(a){ //这边是参数a传入子类的构造函数,然后子类在传递过去给父类
//这边的话就必须显示指定父类的构造函数,因为父类没有没带参数的构造函数了
cout << age << endl;
age = 9;
}

};

int main(){
undergraduate aa(5);
cout << aa.age << endl;
return 0;
}

这边的显示指定,也不说两个构造函数的参数必须一样,而是父类里面的参数必须是存在的,有来源依据的才行

隐藏

  • 当子类和父类拥有相同的同名的函数的时候,父类的会被隐藏,无法直接调用,除非显示指定了

具体还是看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name(int a){
cout << "111" << endl;
}

};

class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
string course; //课程
void print_name(string a){ //如果是bool不会有报错,1会被隐式转化为true
cout << "222" << endl;
}

};

int main(){
undergraduate aa;
// aa.print_name(1); //这边会报错,因为父类的print_name被隐藏了
return 0;
}

当然也可以解除隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name(int a){
cout << "111" << endl;
}

};

class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
using student :: print_name; //显式“解除隐藏”,把父类中所有 print_name(...) 函数重新引入到了子类作用域中
string course; //课程
void print_name(string a){ //如果是bool不会有报错,1会被隐式转化为true
cout << "222" << endl;
}

};

int main(){
undergraduate aa;
aa.print_name(1); //就会输出111
return 0;
}

protected关键字

现在了解完类的派生继承之后,可以来了解这个protected了

  • protected在没有派生继承的情况下,他的作用和private是一样的

但如果有派生继承的情况下

  • 公有继承的时候,父类中除了public被继承,protected也会被继承(继承到子类中的protected),但是只能在类中使用,类外还是无法访问的

具体看如下的代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name();
private:
string name = "lin";
void print_age();

protected:
int iq = 77;
};




class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
string course; //课程
// cout << iq << endl; //类内是不允许有执行语句的,只运行声明和定义,除非是类内的函数
void test(){
cout << iq << endl;
}
};

class postgraduate : public student { //研究生
public:
string research; //研究方向
};



int main(){
undergraduate aa;
cout << aa.age << endl;
// cout << aa.iq << endl; //私有的一样不能继承

return 0;
}

其实继承如果可以分为如下的几种

image-20250629142530553

所以在保密优先级上,private最私密,protected其次,public最公开,private是无论如何都不能被继承的

类的指针

类也是有指针的

有两种写法

第一种

1
2
3
4
5
6
7
int main(){
student *p; //新建一个student类的指针
student aa;
p = &aa; //指针指向aa对象
p -> print_name(1); //然后可以调用里面的方法
return 0;
}

第二种

1
2
3
4
5
int main(){
student *p = new student;
p -> print_name(1); //然后可以调用里面的方法
return 0;
}
  • 父类指针可以指向子类对象,但是不能调用里面的属性和方法

  • 但是子类指针是不可以指向父类对象的

具体还是看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <string>
#include <iostream>
using namespace std;

class student {
public: //注意类中的成员默认是私有的,必须显示加上publick才能初始化对象和访问
int age = 7;
void print_name(int a){
cout << "111" << endl;
}

};

class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
using student :: print_name; //显式“解除隐藏”,把父类中所有 print_name(...) 函数重新引入到了子类作用域中
string course; //课程
void print_name(string a){ //如果是bool不会有报错,1会被隐式转化为true
cout << "222" << endl;
}

};

int main(){
student *p1; //创建一个student父类指针
undergraduate *p2; //创建一个undergraduate子类指针
student aa;
undergraduate bb;
p1 = &aa; //父类指针指向父类对象
p1 = &bb; //父类指针指向子类对象
// p1 - > print_name("11"); //这时候就会报错,因为p1的编译类型是student*,要想要父类指针调用子类方法,就得再结合虚函数
// p2 = &aa; //这个就会报错
p2 = &bb; //子类指针指向子类对象



return 0;
}

多态与虚函数

  • 加关键字virtual表示这是一个虚函数,写在声明的时候,类外实现的时候不需要写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <string>
#include <iostream>
using namespace std;

class student {
public:
int age = 7;
virtual void print_name(); //前面加了virtual,表示是虚函数

};

void student :: print_name(){ //实现的时候不需要加virtual
cout << "111" << endl;
}


class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
virtual void print_name(){
cout << "222" << endl;
}

};

class postgraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
virtual void print_name(){
cout << "333" << endl;
}

};

int main(){
student *p; //创建一个student父类指针
student aa;
undergraduate bb;
postgraduate cc;
p = &aa;
p -> print_name(); //调用子类的方法
p = &bb;
p -> print_name();
p = &cc;
p -> print_name();
return 0;
}

加了virtual才能支持多态和重写,不加virtual就只能隐藏

1
2
3
4
5
6
7
8
9
10
11
12
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
};

class Derived : public Base {
public:
void f() override { cout << "Derived::f" << endl; }
};

Base* p = new Derived(); //虽然p的类型是Base*,但在下面仍会输出Derived::f
p->f(); // ✅ 输出:Derived::f (多态,运行时绑定)
1
2
3
4
5
6
7
8
9
10
11
12
class Base {
public:
void f() { cout << "Base::f" << endl; }
};

class Derived : public Base {
public:
void f() { cout << "Derived::f" << endl; }
};

Base* p = new Derived();
p->f(); // ❌ 输出:Base::f (静态绑定,不管对象是 Derived)
  • 即使 p 实际指向的是 Derived,也不会调用子类的函数
  • 因为 没有 virtual,编译器在编译时就绑定了 Base::f()

C++多态的核心,就是只要在程序开始时候设置一个父类指针,之后这个指针可以动态的指向不同的类,并且指针还可以动态的调用不同的类的方法,从而实现了不同数据类使用相同的方法,重载是编译时决定,多态是运行时决定

纯虚函数与抽象类

具体看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <string>
#include <iostream>
using namespace std;

class student {
public:
int age = 7;
virtual void print_name() = 0; //这时候就表示是纯虚函数,只要有至少一个纯虚函数,那么这个类就是抽象类
void stu(){ //抽象类只是不能实例化,但是仍然可以声明定其他函数,只是纯虚函数本身不能直接定义,
string name;
}

};

class undergraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
// virtual void print_name(){
// cout << "222" << endl;
// }

void print_name() override{ //必须父类的纯虚函数进行重新(override),这两种写法都可以,不然这个子类也会变成抽象类,无法实例化
cout << "222" << endl;
}

};

class postgraduate : public student { //本科生定义,冒号表示由父类student派生而来
public:
virtual void print_name(){
cout << "333" << endl;
}

};

int main(){
student *p; //创建一个student父类指针
// student aa; //这时候就会报错,抽象类是不允许实例化的
undergraduate bb; //不过他的子类是可以实例化的
return 0;
}

评论

评论区