【C++】C++类和对象1:从struct到class,揭开面向对象编程的第一层面纱
·
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
前言
如果你刚从C语言的结构体(struct)来到C++,可能会问:C++的类(class)到底是什么?它和struct有什么区别?为什么类里面既能放变量又能放函数?还有,那个神秘的this指针又是怎么回事?
💡 带着疑问,我们从一个简单的Stack类和Date类出发,一步步拆解C++类的核心概念。你会学到:
- public和private到底在保护什么
- 类的声明和定义为什么要分开写
- 对象在内存里长什么样,占多少字节
- this指针是编译器偷偷塞给你的秘密武器
一、类的定义格式
1.1 一个简单的Stack类
🐾 类定义的格式
class Stack
{
// 类的方法/成员函数
void Init(); // 开头没有访问限定符,默认为private
public:
void Push(int x);
int Top();
void Destroy(); //public内的成员可直接访问,直到下一个访问修饰符的出现
private:
// 类的属性/成员变量
int* arr;
int top;
int capacity;
}; //到 } 结束
1.2 访问限定符
| 限定符 | 含义 |
|---|---|
| public | 公开的,类内外都可以访问 |
| private | 私有的,只能在类内部访问 |
| protected | 受保护的,类内部和派生类可访问(继承时用到) |
🐾 规则:
- 类中如果没有写访问限定符,
默认是private - 访问限定符的作用范围从它出现开始,到下一个限定符或类结束为止
int main()
{
Stack st;
st.Push(1); // ✅ public,可以访问
st.Init(); // ❌ private,无法访问
st.arr; // ❌ private,无法访问
return 0;
}
二、struct vs class
C++兼容C语言的struct,同时引入了class。
| 对比项 | struct | class |
|---|---|---|
| 默认访问权限 | public | private |
| 使用场景 | 通常用于简单的数据聚合 |
通常用于完整的类设计 |
// C++中的struct(默认public)
struct Date
{
void Init(int year, int mouth, int day)
{
_year = year;
_mouth = mouth;
_day = day;
}
private:
int _year;
int _mouth;
int _day;
};
// 链表节点的写法对比
typedef struct ListNodeC // C风格
{
int val;
struct ListNodeC* next;
} LTNodeC;
struct ListNodeCPP // C++风格
{
int val;
ListNodeCPP* next; // 不需要重复写struct
};
建议:用class定义类,用struct定义简单的数据集合。
三、成员变量的命名规范
为了避免成员变量和函数参数冲突,C++中常见的命名习惯:
class Date
{
public:
void Init(int year, int mouth, int day)
{
_year = year; // 成员变量加下划线前缀
_mouth = mouth;
_day = day;
}
private:
int _year; // 方式1:_year
int mouth_; // 方式2:mouth_
int m_day; // 方式3:m_day
};
🐾 常见风格:
1、_year(前缀下划线)2、year_(后缀下划线)3、m_year(m前缀)
选择一种,保持一致即可。
四、类域与声明定义分离
4.1 什么是类域?
类中的成员(变量和函数)属于类域。编译器查找一个名字时,会按照:局部域 -> 全局域 -> 类域的顺序查找。
4.2 声明和定义分离
成员函数可以在类内声明,在类外定义。类外定义时需要指定类域。
class Stack
{
public:
void Init(int n = 4); // 声明
private:
int* arr;
int top;
int capacity;
};
// 类外定义,必须加 Stack:: 指定类域
void Stack::Init(int n)
{
arr = (int*)malloc(sizeof(int) * n);
if (arr == nullptr)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
如果不指定类域会怎样?
void Init(int n) // ❌ 编译器把Init当成全局函数,找不到arr等成员
{
arr = (int*)malloc(...); // 报错:arr未定义
}
五、实例化:从类到对象
-
类: 是一种抽象描述,定义了有哪些成员变量和成员函数。成员变量只是声明,不分配空间。
-
对象: 用类类型在物理内存中创建的具体实体。实例化对象时,才会给成员变量分配空间。
class Date
{
public:
//初始化函数,给日期类的成员变量赋值
void Init(int year, int mouth, int day)
{
//给类的私有成员(private)_year/_mouth/_day赋值
_year = year;
_mouth = mouth;
_day = day;
}
private:
int _year; // 声明,还未分配空间
int _mouth;
int _day;
};
int main()
{
Date d1; // 实例化对象d1,此时才分配空间
Date d2; // 实例化对象d2,分配独立的空间
return 0;
}
类比:类 = 建筑设计图,对象 = 根据图纸盖出来的房子。
六、对象的大小计算
6.1 成员函数不占对象空间
对象中只存储成员变量,成员函数存储在代码段,所有对象共享。
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
----------------------------------------
class B
{
public:
void Print()
{
//...
}
};
----------------------------------------
class C
{
};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl; // 8(char对齐后占4 + int占4)
cout << sizeof(b) << endl; // 1(空类占1字节,用于标识对象地址)
cout << sizeof(c) << endl; // 1(空类占1字节)
}
6.2 对象大小计算规则
| 规则 | 说明 |
|---|---|
只计算成员变量 |
成员函数不计入 |
内存对齐 |
和C语言结构体对齐规则一致 |
空类占1字节 |
用于区分不同的空对象 |
七、this指针
7.1 this指针的本质
在C++中,成员函数被编译时,编译器会隐式地添加一个参数:指向当前对象的指针this。
类的成员函数中访问成员变量,本质都是通过 this 指针访问的
class Date
{
public:
void Init(int year, int mouth, int day)
{
_year = year;
_mouth = mouth;
_day = day;
}
};
// 编译器实际处理的形式
class Date
{
public:
void Init(Date* const this, int year, int mouth, int day)
{
this->_year = year;
this->_mouth = mouth;
this->_day = day;
}
};
// 调用时
Date d1;
d1.Init(2026, 3, 22);
// 编译器转换为:d1.Init(&d1, 2026, 3, 22);
7.2 this指针的特性
| 特性 | 说明 |
|---|---|
| 类型 | ClassName* const this(指针本身不可改,指向的内容可改) |
| 传递方式 | 编译器自动传递,不需要程序员手动传 |
| 存储位置 | 通常是寄存器(如ECX)或栈 |
| 是否可以为空 | 不访问成员变量时,this可以是nullptr |
7.3 经典面试题:空指针调用成员函数
class A
{
public:
void Print()
{
cout << "A::Print()" << endl; // 没有访问成员变量
}
void PrintA()
{
cout << _a << endl; // 访问了成员变量
}
private:
int _a = 1;
};
int main()
{
A* p = nullptr;
p->Print(); // ✅ 可以运行!因为没有解引用this
// p->PrintA(); // ❌ 崩溃!因为this是nullptr,访问_a相当于this->_a
return 0;
}
结论:空指针可以调用不访问成员变量的成员函数,但不能调用访问成员变量的成员函数。
八、知识点汇总
| 知识点 | 核心要点 |
|---|---|
| 访问限定符 | public(公开)、private(私有)、protected(保护) |
| 默认权限 | class默认private,struct默认public |
| 类域 | 查找顺序:局部 → 全局 → 类域 |
| 声明定义分离 | 类外定义需加类名::指定类域 |
| 实例化 | 类不占空间,对象实例化时分配空间 |
| 对象大小 | 只算成员变量,遵循内存对齐,空类占1字节 |
| this指针 | 编译器隐式传递的指向当前对象的指针 |
| 空指针调用 | 不访问成员变量时可以,访问则会崩溃 |
九、总结
- 访问限定符实现了封装,让代码更安全
- 类域影响了编译器查找名字的规则
- 实例化是从模型到实体的过程
- 对象大小只包含成员变量,不包含成员函数
- this指针是编译器在幕后帮我们干的活
十、本文所有的代码
#define _crt_secure_no_warnings 1
#include<string>
#include<iostream>
using namespace std;
////类定义的格式
class stack
{
//类的方法/成员函数
void init(); //开头没有访问限定符修饰,则默认为private
public:
void push(int x);
int top();
void destroy(); //public内的成员可直接访问,直到下一个访问修饰符的出现,也就是下面的private
private:
//类的属性/成员变量
int* arr;
int top;
int capacity;
//后面没有访问限定符出现时,到 } 结束
};
int main()
{
stack st;
st.push(1);
st.top();
st.init(); //err:无法访问 private 成员
st.arr; //err:无法访问 private 成员
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
//
class date
{
public:
void init(int year, int mouth, int day)
//下划线_+名称(_year,_mouth,_day)是为了怕起冲突所加的特殊标记,
//避免输入year时系统不知道用publi的还是private的
{
_year = year;
_mouth = mouth;
_day = day;
}
private:
//为了区分成员变量,一般习惯上在成员变量上加一个特殊标记,如:在前面或后面加 _ 或 m开头
int _year; //_year,year_,myear
int _mouth;
int _day;
};
int main()
{
date date;
date.init(2026, 3, 22);
return 0;
}
struct date
{
void init(int year, int mouth, int day)
{
_year = year;
_mouth = mouth;
_day = day;
}
private:
int _year;
int _mouth;
int _day;
};
//c++兼容c中struct的用法
// struct 定义类时,没有被访问限定符修饰时,默认是private
typedef struct listnodec
{
int val;
struct listnodec* next;
}ltnodec;
struct listnodecpp
{
int val;
listnodecpp* next;
};
int main()
{
date date;
date.init(2026,3,22);
return 0;
}
//stack(类域)
//类域影响的是编译的查找规则,下面程序中init
//1.如果不指定类域stack,那么编译器就会把init当成全局函数,编译时找不到arr等成员的声明/定义在哪里,就会报错
//2.指定类域stack,就是知道init是成员函数,当前域找不到arr等成员,就会到类域中去找
class stack
{
public:
//成员函数
void init(int n = 4);
private:
//成员变量
int* arr;
int top;
int capacity;
};
////////////////////////////////////////////////////////////////////////////////////////
//声明和定义分离,需要指定类域
//void init();//err:由于没指定类域stack,则当成全局函数,
//但是在全局中和局部中均没有arr等成员的声明和定义,则会报错
void stack::init(int n)//此时指定了类域后,除了全局和部局,编译器还会在指定类域中查找成员
{
arr = (int*)malloc(sizeof(int) * n);
if (arr == nullptr)
{
perror("malloc申请空间失败咯!");
return;
}
capacity = n;
top = 0;
}
int main()
{
stack st;
st.init();
return 0;
}
////实例化:用类类型在物理内存中创建对象的过程
////类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,
////这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间
class date
{
public:
//初始化函数,给日期类的成员变量赋值
void init(int year, int mouth, int day)
{
//给类的私有成员(private)_year/_mouth/_day赋值
_year = year;
_mouth = mouth;
_day = day;
}
void print()
{
cout << _year << "/" << _mouth << "/" << _day;
}
private:
//加了static就变成了静态成员变量:属于类本身,所有对象公用一份_year
static int _year;
//普通成员变量:属于每个对象,每个对象都有自己独立的_mouth,_day
int _mouth;
int _day;
};
//静态成员变量不能在类内初始化,必须在类外、全局作用域初始化
//格式为 类型 类名::静态成员名 = 初始值;
int date::_year = 0;
int main()
{
//date类实例化出对象d1和d2
date d1;
date d2;
d1.init(2026, 3, 22);
d1.print();
d2.init(2026, 3, 22);
d2.print();
return 0;
}
/////计算一下a/b/c实例化的对象多大
class a
{
public:
void print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
class b
{
public:
void print()
{
//....
}
};
class c
{
};
int main()
{
a a;
b b;
c c;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
return 0;
}
////this指针
////类的成员函数中访问成员变量,本质都是通过 this 指针访问的
////如 init 函数中给 _year 赋值:this->_year = year;
class date
{
public:
void init(int year, int mouth, int day)
//void init(date* const this,int year,int mouth,int day)
{
_year = year;
_mouth = mouth;
_day = day;
//this->_year = year;
//this->mouth = mouth;
//this->_day = day;
}
void print() //这是我们写代码时的简洁写法
//void print(date* const this):这是编译器底层处理的形式
//this是指向当前调用该函数的date对象的指针
{
cout << _year << "/" << _mouth<<"/" << _day << endl;
//cout<<this->_year<<"/"<<this->_mouth<<"/"<<this->_day<<endl;
}
private:
static int _year;
int _mouth;
int _day;
};
int main()
{
//date类实例化出对象d1和d2
date d1;
date d2;
d1.init(2026, 3, 22);
//d1.init(&d1,2026,3,22);
d1.print();
//d1.print(&d1);
d2.init(2026, 3, 22);
//d2.init(&d2,2026,3,22)
d2.print();
//d2.print(&d2);
return 0;
}
class a
{
public:
void print()
{
cout << "a::print()" << endl;
//cout<<_a<<endl;
}
private:
int _a = 1;
};
int main()
{
a* p = nullptr;
p->print();
//p->_a=1;
return 0;
}
🐾 下一篇我们继续学习:
- Date类的设计与实现
- 运算符重载
- 拷贝构造函数
- 流插入和流提取(cout、cin)
谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论
更多推荐

所有评论(0)