[(4) c++中的类和结构体的区别](#(4) c++中的类和结构体的区别)
c++面向对象的三大特性:封装、继承、多态。
一、封装
封装的意义:将属性(变量)和行为(函数)作为一个整体,表现生活中的事物;将属性和行为加以权限控制。
语法:class 类名 { 访问权限 : 属性/行为 }
(1)类的定义与简单调用
cpp
#include<iostream>
using namespace std;
class Circle{
//权限
public:
//属性
int r;
//行为:求解半径为r的圆的周长
double calculateC() {
return 2 * 3.14 * r;
}
};
int main(){
//创建对象
Circle c;
//调用
c.r = 10;
cout << c.calculateC() << endl;
return 0;
}
上面的代码中,Circle类中包含public访问权限的属性和行为(函数),可以通过创建对象进行调用。
(2)对象赋值与取值
一般通过下面的格式进行对对象进行赋值和取值。
cpp
#include<iostream>
using namespace std;
class Student{
private:
int s_age;
string s_name;
public:
void setName(string name){
s_name = name;
}
void setAge(int age){
s_age = age;
}
int getAge(){
return s_age;
}
void show_student(){
cout << "name: " << s_name << " age: " << s_age << endl;
}
};
int main(){
//创建对象
Student s;
//调用
s.setName("小刚");
s.setAge(18);
s.show_student();//输出 name: 小刚 age: 18
}
其中,私有属性只能通过类内部的成员函数(通常是get/set方法)访问和修改,避免外部代码直接操作属性,从根本上防止数据被非法、错误地修改;这样也能检测数据的有效性。
(3)对不同属性和行为进行权限划分
访问权限有三种:分别为
|-----------|------|----------|----------|
| 权限 || 类内访问 | 类外访问 |
| public | 公共权限 | √ | √ |
| protected | 保护权限 | √ | × |
| private | 私有权限 | × | × |
(4) c++中的类和结构体的区别
两者其实非常相像,都可以在其中定义属性和行为,但是类和结构体是具有本质区别的:
- 类(class)和结构体(struct)的基本格式有所不同(这里不做赘述)
- 类和结构体的默认访问权限不同: 类中默认权限为private、结构体的默认权限为public
- 类和结构体的默认继承权限不同。(后续做讲述)
cpp
#include<iostream>
using namespace std;
class C1{
int a = 1;// 访问失败
};
struct C2{
int a = 2;
};
int main(){
C1 c1;
cout << c1.a << endl;
C2 c2;
cout << c2.a << endl;
return 0;
}
二、继承
**继承的意义:**子类可以从父类根据相应的权限继承相应的成员函数或成员变量,从而减少冗余代码,提高代码利用率。
**语法:**class 子类名:子类从父类继承的权限 父类名{}
以下面的示例代码为例:
cpp
#include<iostream>
using namespace std;
class A {
};
class B :public A {
};
int main() {
}
这里就是类B继承类A,类A称为父类/基类,类B称为子类/派生类。
(1)派生类中的成员组成
派生类中的成员组成 = 从父类继承下来的 + 子类新增的成员。
父类继承→共性,子类新增→个性
(2)继承的类别
根据子类从父类中继承的权限,可以将继承分为公共继承、保护继承和私有继承。
这是由关键字public、protected和private决定的,而子类继承父类的权限事实上只能缩小权限,不能放大权限。下面的例子能够说明:
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B :public A {
void f() {
this->a;
this->b;
this->c;
//不可访问,是因为父类中的private权限到子类中尽管提供的继承权限是public,但是也是无法访问的。
//不能放大权限,只能缩小权限或权限不变
}
};
int main() {
}
这里要注意:子类是能够继承到父类中受保护的权限的。
(3)继承中的对象模型
从父类中继承的成员都会包含在子类的对象中,包含父类私有。这里"能否访问"和"是否继承下来"是不同的问题。
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B :public A {
public:
int d;
};
int main() {
cout << sizeof(B);//输出 16
return 0;
}
(4)继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数,构造函数和析构的调用顺序可以通过下面的程序得知(和对象成员与本类的调用顺序相似):
cpp
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "父类构造函数!" << endl;
}
~A()
{
cout << "父类析构函数!" << endl;
}
};
class B : public A
{
public:
B()
{
cout << "子类构造函数!" << endl;
}
~B()
{
cout << "子类析构函数!" << endl;
}
};
int main() {
B b;
return 0;
}
顺序为:父类构造→子类构造→子类析构→父类析构
这种顺序的原因如下:
- 构造顺序原因:
- 依赖关系:子类对象通常包含父类的部分(通过继承获得的成员变量和成员函数)。在创建子类对象时,首先需要确保父类部分被正确初始化,因为子类的功能可能依赖于父类的正确状态
- 资源分配和初始化顺序:父类可能需要在构造函数中打开一个文件或者建立一个网络连接,这些资源是子类可能会使用的基础资源
- 析构顺序原因:
- 资源释放顺序:子类对象包含父类部分,所以在析构时应该先释放子类特有的资源,然后再释放父类的资源。如果先析构父类,可能会导致子类在使用父类资源时出现错误,因为这些资源已经被释放了
- 保证完整性:避免在子类析构过程中依赖已被破坏的父类状态
(5)继承同名成员处理方式
这里只需要记住:访问子类同名成员 直接访问即可; 访问父类同名成员 需要加作用域
比如下面的类A和类B中都有public权限的成员变量Bs,那么按理来说B类继承A类,也会继承A类的这个成员的,但事实上子类对象正常访问成员的成员就是子类的,不是父类的。
如果想要访问父类的成员,可以通过下面两种方式访问:
- 父类对象直接访问
- 父类名::成员名 的格式访问,也就是要在前面加上作用域
cpp
#include<iostream>
using namespace std;
class A
{
public:
int Bs = 1;
};
class B : public A
{
public:
int Bs = 100;
void f() {
cout << Bs << endl; //输出100
cout << A::Bs << endl; //输出1
}
};
int main() {
B b;
b.f();
return 0;
}
(6)继承同名静态成员的处理方式
访问子类同名成员 直接访问即可; 访问父类同名成员 需要加作用域
cpp
#include<iostream>
using namespace std;
class A {
public:
static int a;
static void show() { cout << "A::show()" << endl; }
};
int A::a = 100;
class B : public A {
public:
static int a;
static void show() { cout << "B::show()" << endl; }
void test() {
cout << a << endl; //输出200 访问子类B的a(默认)
cout << A::a << endl; //输出100 显式访问父类A的a
show(); //输出"B::show()" 访问子类B的show()(默认)
A::show(); //输出"A::show()" 显式访问父类A的show()
}
};
int B::a = 200;
int main() {
B b;
b.test();
return 0;
}
这个程序里面,分别在类A和类B中创建了静态成员变量a和静态成员函数show(),如果在子类中直接访问同名成员,那就直接访问,否则就要加上父类的作用域,进而访问。
(7)多继承
多继承指的是一个类继承多个类,多继承过程中可能导致父类子类中同名成员的产生,需要加上作用域进行调用。在C++实际开发中不建议多继承。
下面就是典型的多继承的例子:
cpp
#include<iostream>
using namespace std;
class A1 {
};
class A2 {
};
class B :public A1, public A2 {
};
多继承可能会导致菱形继承的产生,即多继承之后出现一种情况------子类继承两个父类,这两个父类同时继承于其他父类。
比如说↓

这样会导致类B收到两份类A中的继承成员,可能会导致内存浪费或内存不一致。
可以通过在中间类A1和A2的声明中加**关键字"virtual"**解决。
cpp
#include<iostream>
using namespace std;
class A {
};
class A1 :virtual public A {
};
class A2 :virtual public A {
};
class B :public A1, public A2 {
};
三、多态
(1)函数重写
子类重新定义父类中有++相同的名称,返回值和参数++ 的虚函数(有virtual关键字),主要在继承关系中出现。必须位于基类和子类中。重写的函数和被重写的函数的返回值、函数名和参数必须完全一致。
子类中的某个函数后如果声明了override关键字,说明其一定是有对基类的对应函数进行重写的。
cpp
class Shape {
public:
virtual void draw() = 0;
};
class Circle :public Shape {
public:
void draw() override {
cout << "绘制圆形" << endl;
}
};
(2)函数隐藏
子类重新定义父类中有相同的名称,只要不是重写就是隐藏。
(3)多态的基本条件:
- 发生函数重写
- 重写和被重写的函数必须是virtual函数,并且分别位于基类和子类中。
cpp
#include<iostream>
using namespace std;
class A {
public:
virtual void f() {
}
};
class B :public A{
virtual void f() {
cout << "重写后的函数f" << endl;
}
};
int main() {
}
(4)多态的类别
-
静态多态:包括函数重载、运算符重载;更早绑定函数地址,编译阶段确定
-
动态多态:通过派生类和虚函数实现运行时多态;更晚绑定函数地址,运行阶段确定
(5)多态的使用
多态的使用:父类的指针指向子类的对象。
cpp
#include<iostream>
using namespace std;
class A {
public:
virtual void f() {
cout << "函数f" << endl;
}
};
class B :public A{
public:
virtual void f() {
cout << "重写后的函数f" << endl;
}
};
int main() {
A* a = new B();
a->f();
return 0;
}
(6)纯虚函数和抽象类
纯虚函数: 格式:virtual 类型 函数名() = 0
**抽象类:**当类中有了纯虚函数,这个类就成为抽象类。
**抽象类特点:**无法实例化对象;子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
问:下面那个类是抽象类?
cpp
#include<iostream>
using namespace std;
class A {
//纯虚函数
virtual void f() = 0;
};
class B :public A{
};
class C :public A {
void f() {
}
};
int main() {
return 0;
}
上面的程序中类A里面有一个纯虚函数f,所以类A是抽象类,类B和类C都继承类A,但是类B因为没有重写纯虚函数f,所以类B是抽象类,类C因为重写了纯虚函数f,所以类C不是抽象类。
(7)虚析构和纯虚析构
多态使用过程中,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。这可以通过++将父类中的析构函数改为虚析构或者纯虚析构++解决。
共同点: 这两个析构可以**解决父类指针释放子类对象(这是核心用途!)**的问题,且都需要有具体的函数实现。
**区别:**如果该类中包含纯虚析构,那么该类属于抽象类,无法实例化对象。
**注:**一个类能够实例化对象当且仅当其能够创建对象。
**纯虚析构的格式为:**virtual ~类名() = 0
**虚析构的格式为:**virtual ~类名()
cpp
#include<iostream>
using namespace std;
class A {
public:
int* p; // 动态内存成员
A() {
p = new int(10);
cout << "[A] 构造:p分配内存,值=" << *p << endl;
}
// 关键:虚析构(有函数体,类内实现)
virtual ~A() {
delete p;
p = nullptr;
cout << "[A] 析构:p已释放" << endl;
}
};
class B : public A {
public:
int* p1; // B自己的动态内存成员
B() {
p1 = new int(20);
cout << "B构造:p1分配内存,值=" << *p1 << endl;
}
// 子类析构
~B() {
delete p1; // 释放B的动态内存
p1 = nullptr;
cout << "B析构:p1已释放" << endl;
}
};
int main() {
cout << "测试1:基类指针指向子类对象" << endl;
A* ptr = new B(); // A指针指向B对象
delete ptr; // 虚析构触发:先B析构 → 再A析构
cout << "\n测试2:直接实例化A(虚析构允许)" << endl;
A a; // A可实例化,析构时正常释放p
return 0;
}
运行结果如下:

cpp
#include<iostream>
using namespace std;
// 基类A:纯虚析构(抽象类,不可实例化)
class A {
public:
int* p;
A() {
p = new int(10);
cout << "[A] 构造:p分配内存,值=" << *p << endl;
}
virtual ~A() = 0;
};
// 纯虚析构的函数体
A::~A() {
delete p;
p = nullptr;
cout << "[A] 析构:p已释放" << endl;
}
class B : public A {
public:
int* p1;
B() {
p1 = new int(20);
cout << "[B] 构造:p1分配内存,值=" << *p1 << endl;
}
// 子类析构(自动覆盖纯虚析构)
~B() override {
delete p1;
p1 = nullptr;
cout << "[B] 析构:p1已释放" << endl;
}
};
int main() {
cout << "测试1:基类指针指向子类对象" << endl;
A* ptr = new B();
delete ptr; // 仍触发:B析构 → A析构
// cout << "\n===== 测试2:尝试实例化A(编译报错) =====" << endl;
// A a; // A是抽象类(纯虚析构导致)
return 0;
}
运行结果如下:
