文章目录
- 一、C的struct和C++的类的区别
- 二、关于OOP
- 三、举例:一个商品类CGoods
- 四、构造函数和析构函数
- 1、定义一个顺序栈
- 2、用构造和析构代替s.init(5);和s.release();
- 3、在不同内存区域构造对象
- 4、深拷贝和浅拷贝
- 5、构造函数和深拷贝的简单应用
- 6、构造函数的初始化列表
- 五、类的各种成员变量和成员方法
- 1、普通成员变量、静态成员变量
- 2、普通成员方法、静态成员方法、常成员方法
- 六、指向类成员(成员变量和成员方法)的指针
一、C的struct和C++的类的区别
以下表格由DeepSeek-R1生成:
特性 | C 的 struct | C++ 的 struct | C++ 的 class |
---|---|---|---|
默认访问权限 | 无(仅数据) | public | private |
成员函数 | 不支持 | 支持 | 支持 |
继承/多态 | 不支持 | 支持 | 支持 |
构造/析构函数 | 不支持 | 支持 | 支持 |
模板 | 不支持 | 支持 | 支持 |
设计用途 | 纯数据聚合 | 简单数据+方法 | 封装复杂对象行为 |
实际开发中,C++ 的 struct 和 class 仅默认权限不同,但习惯上用 struct 表示数据为主的结构,class 表示具有复杂行为的对象。
- 在C中:
各种各样的函数的定义、struct - 在C++中:
实体(属性、行为) -> ADT(abstract data type)
对象 <-(实例化) 类(属性->成员变量行为->成员方法)
二、关于OOP
- Object Oriented Programming:面向对象程序设计
- OOP语言的四大特性:抽象、封装/隐藏、继承、多态
- 类中的访问限定符:(由DeepSeek-R1生成)
基类成员访问限定符 | 类内部访问 | 继承方式 | 派生类中基类成员的访问权限 | 外部代码访问 | 友元访问 |
---|---|---|---|---|---|
public | ✔ | public 继承 | public | ✔ | ✔ |
protected 继承 | protected | ✖ | ✔ | ||
private 继承 | private | ✖ | ✔ | ||
protected | ✔ | public 继承 | protected | ✖ | ✔ |
protected 继承 | protected | ✖ | ✔ | ||
private 继承 | private | ✖ | ✔ | ||
private | ✔ | 任何继承方式 | 不可访问 | ✖ | ✔ |
三、举例:一个商品类CGoods
#include <iostream>
using namespace std;
const int NAME_LEN = 20;
class CGoods {
public: // 给外部提供公有的成员方法,来访问私有的属性
// 商品数据初始化
void init(const char *name, double price, int amount);
// 打印商品信息
void show();
// 给成员变量提供getXXX或setXXX的方法(注意:类内部实现的方法,自动处理成inline内联函数)
void setName(const char *name) { strcpy_s(_name, sizeof(_name), name); }
void setPrice(double price) { _price = price; }
void setAmount(int amount) { _amount = amount; }
const char *getName() { return _name; }
double getPrice() { return _price; }
int getAmount() { return _amount; }
private:
char _name[NAME_LEN];
double _price;
int _amount;
};
void CGoods::init(const char *name, double price, int amount) {
strcpy_s(_name, sizeof(_name), name);
_price = price;
_amount = amount;
}
void CGoods::show() {
cout << "name: " << _name << endl;
cout << "price: " << _price << endl;
cout << "amount: " << _amount << endl;
}
int main() {
CGoods good1;
good1.init("面包", 10.0, 200);
good1.show();
good1.setPrice(20.5);
good1.setAmount(100);
good1.show();
CGoods good2;
good2.init("空调", 10000.0, 50);
good2.show();
return 0;
}
注意:
- 类可以定义无数个对象,每一个对象都有自己的成员变量,但是它们共享一套成员方法。
- 类内部实现的方法,自动处理成inline内联函数,外部则不会。
- 对象的内存大小,与成员变量有关。VS2022下可以通过cl C++面向对象.cpp /dlreportSingleClassLayoutCGoods查看占用内存大小。
- init(name,price,amount)怎么知道处理哪个对象的信息,把信息初始化给哪一个对象的呢?
类的成员方法一经编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址。
void init(CGoods *this, const char *name, double price, double amount);
void CGoods::init(CGoods *this, const char *name, double price, int amount) {
strcpy_s(this->_name, sizeof(this->_name), name);
this->_price = price;
this->_amount = amount;
}
init(&good1, "面包", 10.0, 200);
四、构造函数和析构函数
1、定义一个顺序栈
#include <iostream>
using namespace std;
const int NAME_LEN = 20;
class SeqStack {
public:
void init(int size = 10) {
_pstack = new int[size];
_top = -1;
_size = size;
}
void release() {
delete[]_pstack;
_pstack = nullptr;
}
void push(int val) {
if (full()) {
resize();
}
_pstack[++_top] = val;
}
void pop() {
if (empty()) {
return;
}
--_top;
}
int top() {
return _pstack[_top];
}
bool empty() { return _top == -1; }
bool full() { return _top == _size - 1; }
private:
int *_pstack;
int _top;
int _size;
void resize() {
int* ptmp = new int[_size * 2];
for (int i = 0; i < _size; ++i) {
ptmp[i] = _pstack[i];
} // memcpy(ptmp, _pstack, sizeof(int)*size);或realloc可能产生深拷贝浅拷贝的问题
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main() {
SeqStack s;
s.init(5);
for (int i = 0; i < 15; ++i) {
s.push(rand() % 100);
}
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
s.release();
return 0;
}
2、用构造和析构代替s.init(5);和s.release();
#include <iostream>
using namespace std;
const int NAME_LEN = 20;
class SeqStack {
public:
SeqStack(int size = 10) { // 构造函数可带参数,可以重载
_pstack = new int[size];
_top = -1;
_size = size;
}
~SeqStack() { // 不带参数,只能有一个
delete[]_pstack;
_pstack = nullptr;
}
void push(int val) {
if (full()) {
resize();
}
_pstack[++_top] = val;
}
void pop() {
if (empty()) {
return;
}
--_top;
}
int top() {
return _pstack[_top];
}
bool empty() { return _top == -1; }
bool full() { return _top == _size - 1; }
private:
int *_pstack;
int _top;
int _size;
void resize() {
int* ptmp = new int[_size * 2];
for (int i = 0; i < _size; ++i) {
ptmp[i] = _pstack[i];
} // memcpy(ptmp, _pstack, sizeof(int)*size);或realloc可能产生深拷贝浅拷贝的问题
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main() {
//1.开辟内存 2.调用构造函数
SeqStack s(5);
//s.init(5); // 对象成员变量的初始化
for (int i = 0; i < 15; ++i) {
s.push(rand() % 100);
}
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
//s.release(); // 释放对象成员变量占用的外部堆内存(外部资源)
return 0;
}
3、在不同内存区域构造对象
#include <iostream>
using namespace std;
const int NAME_LEN = 20;
class SeqStack {
public:
SeqStack(int size = 10) {
cout << this << " SeqStack" << endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
~SeqStack() {
cout << this << " ~SeqStack" << endl;
delete[]_pstack;
_pstack = nullptr;
}
void push(int val) {
if (full()) {
resize();
}
_pstack[++_top] = val;
}
void pop() {
if (empty()) {
return;
}
--_top;
}
int top() {
return _pstack[_top];
}
bool empty() { return _top == -1; }
bool full() { return _top == _size - 1; }
private:
int *_pstack;
int _top;
int _size;
void resize() {
int* ptmp = new int[_size * 2];
for (int i = 0; i < _size; ++i) {
ptmp[i] = _pstack[i];
} // memcpy(ptmp, _pstack, sizeof(int)*size);或realloc可能产生深拷贝浅拷贝的问题
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
/*
.data
heap
stack
*/
SeqStack s0; //.data
int main() {
cout << "heap上对象构造" << endl;
SeqStack* ps = new SeqStack(60); // heap malloc内存开辟+SeqStack对象构造
ps->push(70);
ps->push(80);
ps->pop();
cout << ps->top() << endl;
delete ps; // 先调用ps->~SeqStack()+然后free(ps)
cout << "stack上对象构造" << endl;
SeqStack s(5); //stack
for (int i = 0; i < 15; ++i) {
s.push(rand() % 100);
}
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
return 0;
}
打印结果如下:
00007FF7FB3914C0 SeqStack
heap上对象构造
0000026D43E94EF0 SeqStack
70
0000026D43E94EF0 ~SeqStack
stack上对象构造
000000F8EF6FF9D8 SeqStack
61 27 81 45 5 64 62 58 78 24 69 0 34 67 41
000000F8EF6FF9D8 ~SeqStack
00007FF7FB3914C0 ~SeqStack
4、深拷贝和浅拷贝
SeqStack s; // 没有提供任何构造函数的时候,会为你生成默认构造函数
SeqStack s1(10);
SeqStack s2 = s1; // #1 默认拷贝构造函数 -》做直接内存数据拷贝
//Seqstack s3(s1);// #2 同1
上述默认构造函数导致出现浅拷贝的问题,浅拷贝一般包括以下几种问题:
-
多个对象共享同一资源:
如果多个对象的指针成员指向同一块内存,修改其中一个对象会影响其他对象。 -
重复释放资源:
当多个对象的指针指向同一块内存时,析构函数可能会多次释放同一块内存,导致程序崩溃。 -
内存泄漏:
如果资源被浅拷贝后,原始对象的资源没有被正确释放,会导致内存泄漏。
默认拷贝构造函数:浅拷贝
SeqStack(const SeqStack &src) {
_pstack = src._pstack;
_top = src._top;
_size = src._size;
}
深拷贝
SeqStack(const SeqStack &src) {
_pstack = new int[src._size];
for (int i = 0; i <= src._top; ++i) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
如果对两个已经存在的对象进行赋值操作,也会出现浅拷贝问题
int main() {
cout << "开始构造s" << endl;
SeqStack s; // 没有提供任何构造函数的时候,会为你生成默认构造函数
cout << "开始构造s1" << endl;
SeqStack s1(10);
cout << "开始构造s2" << endl;
SeqStack s2 = s1; // #1 默认拷贝构造函数 -》做直接内存数据拷贝
//Seqstack s3(s1);// #2 同1
s2 = s1;
return 0;
}
s2 = s1; // 默认的赋值函数 =》做直接的内存拷贝
修改如下:
// s2.operator=(s1)
// void operator=(const SeqStack &src)
#include <iostream>
using namespace std;
const int NAME_LEN = 20;
class SeqStack {
public:
SeqStack(int size = 10) {
cout << this << " SeqStack" << endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
// 自定义拷贝构造函数
SeqStack(const SeqStack& src) {
cout << this << " const SeqStack& src" << endl;
_pstack = new int[src._size];
for (int i = 0; i <= src._top; ++i) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
// 赋值重载函数
void operator=(const SeqStack& src) {
cout << this << " operator=" << endl;
// 防止自赋值
if (this == &src) {
return;
}
// 需要先释放当前对象占用的外部资源
delete[]_pstack;
_pstack = new int[src._size];
for (int i = 0; i <= src._top; ++i) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
~SeqStack() {
cout << this << " ~SeqStack" << endl;
delete[]_pstack;
_pstack = nullptr;
}
void push(int val) {
if (full()) {
resize();
}
_pstack[++_top] = val;
}
void pop() {
if (empty()) {
return;
}
--_top;
}
int top() {
return _pstack[_top];
}
bool empty() { return _top == -1; }
bool full() { return _top == _size - 1; }
private:
int *_pstack;
int _top;
int _size;
void resize() {
int* ptmp = new int[_size * 2];
for (int i = 0; i < _size; ++i) {
ptmp[i] = _pstack[i];
} // memcpy(ptmp, _pstack, sizeof(int)*size);或realloc可能产生深拷贝浅拷贝的问题
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main() {
cout << "开始构造s" << endl;
SeqStack s; // 没有提供任何构造函数的时候,会为你生成默认构造函数
cout << "开始构造s1" << endl;
SeqStack s1(10);
cout << "开始构造s2" << endl;
SeqStack s2 = s1; // #1 默认拷贝构造函数 -》做直接内存数据拷贝
//Seqstack s3(s1);// #2 同1
s2 = s1;
return 0;
}
打印结果:
开始构造s
0000003B8E14F6C8 SeqStack
开始构造s1
0000003B8E14F6F8 SeqStack
开始构造s2
0000003B8E14F728 const SeqStack& src
0000003B8E14F728 operator=
0000003B8E14F728 ~SeqStack
0000003B8E14F6F8 ~SeqStack
0000003B8E14F6C8 ~SeqStack
5、构造函数和深拷贝的简单应用
- 字符串String
#include <iostream>
using namespace std;
class String {
public:
String(const char* str = nullptr) { // 普通构造函数
if (str != nullptr) {
m_data = new char[strlen(str) + 1];
strcpy_s(m_data, strlen(str) + 1, str);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
String(const String& other) { // 拷贝构造函数
m_data = new char[strlen(other.m_data) + 1];
strcpy_s(m_data, strlen(other.m_data) + 1, other.m_data);
}
~String() {
delete[]m_data;
m_data = nullptr;
}
String& operator=(const String& other) { // 返回值不是void,而是当前类型的引用,是为了连续赋值
if (this == &other) {
return *this;
}
delete[]m_data;
m_data = new char[strlen(other.m_data) + 1];
strcpy_s(m_data, strlen(other.m_data) + 1, other.m_data);
return *this;
}
private:
char* m_data;
};
int main() {
// 调用带const char*参数的构造函数
String str1;
String str2("hello");
String str3 = "world";
// 调用拷贝构造函数
String str4 = str3;
String str5(str4);
// 调用赋值重载函数
str1 = str2;
str3 = str1 = str2; // 连续赋值
return 0;
}
- 循环队列Queue
#include <iostream>
using namespace std;
class Queue {
public:
Queue(int size = 20) {
_pQue = new int[size];
_front = _rear = 0;
_size = size;
}
Queue(const Queue &src) {
_front = src._front;
_rear = src._rear;
_size = src._size;
_pQue = new int[_size];
for (int i = _front; i != _rear; i = (i + 1) % _size) {
_pQue[i] = src._pQue[i];
}
}
Queue& operator=(const Queue& src) {
if (this == &src) {
return *this;
}
delete[]_pQue;
_front = src._front;
_rear = src._rear;
_size = src._size;
_pQue = new int[_size];
for (int i = _front; i != _rear; i = (i + 1) % _size) {
_pQue[i] = src._pQue[i];
}
return *this;
}
~Queue() {
delete[]_pQue;
_pQue = nullptr;
}
void push(int val) {// 入队操作
if (full()) {
resize();
}
_pQue[_rear] = val;
_rear = (_rear + 1) % _size;
}
void pop() { // 出队操作
if (empty()) {
return;
}
_front = (_front + 1) % _size;
}
int front() {
return _pQue[_front];
}
bool full() {
return (_rear + 1) % _size == _front;
}
bool empty() {
return _front == _rear;
}
private:
int* _pQue; // 申请队列的数组空间
int _front; // 指示队头的位置
int _rear; // 指示队尾的位置
int _size; // 队列扩容的总大小
void resize() {
int* ptmp = new int[2 * _size];
int index = 0;
for (int i = _front; i != _rear; i = (i + 1) % _size) {
ptmp[index++] = _pQue[i];
}
delete[]_pQue;
_pQue = ptmp;
_front = 0;
_rear = index;
_size *= 2;
}
};
int main() {
Queue q;
for (int i = 0; i < 20; ++i) {
q.push(rand() % 100);
}
while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
cout << endl;
Queue q1 = q;
q1 = q;
return 0;
}
6、构造函数的初始化列表
#include <iostream>
using namespace std;
class CDate {
public:
CDate(int y, int m, int d) { //自定义的构造函数
_year = y;
_month = m;
_day = d;
}
void show() {
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
/*
构造函数的初始化列表: 可以指定当前对象成员变量的初始化方式
CDate信息 CGoods商品信息的一部分 a part of ... 组合的关系
*/
class CGoods {
public:
// "CDate"没有合适的构造函数可用,因此在这里使用初始化列表
CGoods(const char *n, int a, double p, int y, int m, int d)
: _date(y, m, d)
, _amount(a) // 相当于int _amount = a;直接进行初始化,
// 避免了先定义(int _amount; _amount = a;)需要调用默认构造函数,
// 而CDate因为定义了自定义的构造函数,不会调用默认构造函数
, _price(p) // #1 构造函数的初始化列表
{
// #2 当前类类型构造函数体
strcpy_s(_name, 20, n);
}
void show() {
cout << "name: " << _name << endl;
cout << "amount: " << _amount << endl;
cout << "price: " << _price << endl;
_date.show();
}
private:
char _name[20];
int _amount;
double _price;
CDate _date; // 成员对象 1.分配内存 2.调用构造函数
};
int main() {
CGoods good("商品", 100, 35.0, 2025, 1, 27);
good.show();
return 0;
}
注意:成员变量的初始化和它们定义的顺序有关,和构造函数初始化列表中出现的先后顺序无关!
#include <iostream>
using namespace std;
class Test {
public:
Test(int m = 10):mb(m), ma(mb) {}
void show() {
cout << "ma: " << ma << "mb: " << mb << endl;
}
private:
int ma;
int mb;
};
int main() {
Test t;
t.show(); // ma: -858993460 mb: 10
return 0;
}
Windows下会将未初始化的内存填充为特定的值0xCCCCCCCC(十进制为-858993460),先初始化ma,而此时mb未初始化,其值-858993460,因此ma: -858993460,而mb: 10。
五、类的各种成员变量和成员方法
1、普通成员变量、静态成员变量
2、普通成员方法、静态成员方法、常成员方法
- 普通的成员方法 =>编译器会添加一个this形参变量
1.属于类的作用域
2.调用该方法时,需要依赖一个对象!常对象是无法调用的 实参:const CGoods* -》CGoods *this
3.可以任意访问对象的私有成员 protected继承 public private - static静态成员方法 =>不会生成this形参
1.属于类的作用域
2.用类名作用域来调用方法
3.可以任意访问对象的私有成员,仅限于不依赖对象的成员(只能调用其它的static静态成员) - const常成员方法 const CGoods *this
1.属于类的作用域
2.调用依赖一个对象,普通对象或者常对象都可以
3.可以任意访问对象的私有成员,但是只能读,而不能写(只要是只读操作的成员方法,一律实现成const常成员方法)
#include <iostream>
using namespace std;
const int NAME_LEN = 20;
class CGoods {
public:
CGoods(const char* name, double price, int amount);
void show();
void show() const;
static void showCount();
private:
char _name[NAME_LEN];
double _price;
int _amount;
static int _count; // 声明 用来记录商品对象的总数量;不属于对象,而属于类级别的
};
// static成员变量一定要在类外进行定义并且初始化
int CGoods::_count = 0;
CGoods::CGoods(const char* name, double price, int amount) {
strcpy_s(_name, sizeof(_name), name);
_price = price;
_amount = amount;
_count++; // 记录所有产生的新对象的数量
}
// 普通成员方法 CGoods *this
void CGoods::show() {
cout << "name: " << _name << endl;
cout << "price: " << _price << endl;
cout << "amount: " << _amount << endl;
}
// 常成员方法 const CGoods *this
void CGoods::show() const {
cout << "name: " << _name << endl;
cout << "price: " << _price << endl;
cout << "amount: " << _amount << endl;
}
// 静态成员方法 没有this指针
void CGoods::showCount() {
cout << "所有商品数量: " << _count << endl;
}
int main() {
CGoods good1("面包", 10.0, 200);
good1.show();
CGoods good2("空调", 100.0, 50);
good2.show();
CGoods::showCount();
const CGoods good3("非卖品", 10000.0, 1);
good3.show(); //CGoods::show(&good3) const CGoods* -> CGoods*不可以
return 0;
}
六、指向类成员(成员变量和成员方法)的指针
指向普通成员变量的指针依赖于对象的调用,指向静态成员变量的指针不依赖。
指向普通成员方法的函数指针同样依赖于对象的调用,指向静态成员方法的指针不依赖。
#include <iostream>
using namespace std;
class Test {
public:
void func() { cout << "call Test::func" << endl; }
static void static_func() { cout << "Test::static func" << endl; }
int ma;
static int mb;
};
int Test::mb;
int main() {
Test t1;
Test *t2 = new Test();
// 指向成员变量的指针
// int a=10; int *p=&a; *p=30;
int Test::*p = &Test::ma;
t1.*p = 20;
cout << t1.*p<< endl;
t2->*p = 30;
cout << t2->*p << endl;
// 指向静态成员变量的指针
int *p1 = &Test::mb;
*p1 = 40;
cout << *p1 << endl;
// 指向成员方法的指针
void (Test::*pfunc)() = &Test::func;
(t1.*pfunc)();
(t2->*pfunc)();
// 定义函数指针指向类的静态成员方法
void(*pfunc1)() = &Test::static_func;
(*pfunc1)();
delete t2;
return 0;
}
打印结果如下:
20
30
40
call Test::func
call Test::func
Test::static func