一、类的设计
-
结构体 vs 类
-
C语言结构体
-
成员变量固定大小(如
char name[64]
),无法动态扩展。 -
结构体不能包含函数实现,需通过外部函数操作数据(如
play_game
)。
-
-
C++类
-
支持成员函数(方法),直接封装在类中。
-
成员变量和函数可设置访问权限(
public
、private
、protected
),默认权限为private
。 -
通过公有成员函数(如
setName
、setAge
)间接访问私有成员,实现封装。
-
-
-
类的定义与权限
-
语法结构
-
class ClassName {
public:
// 公有成员(外部接口)
private:
// 私有成员(仅类内访问)
protected:
// 保护成员(类内及派生类访问)
};
- 成员变量
用来描述一个对象的属性信息 ,与一般的变量声明相同,但需要将它存放在类的声明体中
- 成员函数
-
支持内联函数、重载函数、默认参数。
-
可在类内声明原型,类外实现(需用
类名::
限定)。
-
-
如果你尝试在类的外部直接访问或修改私有成员变量,编译器会报错,因为它违反了封装原则,即类的内部实现细节(如私有成员)应该被隐藏起来,只通过公共接口(如公有成员函数)与外部交互
-
struct 与 class 的区别
-
默认访问权限
-
struct
:成员默认public
。 -
class
:成员默认private
。
-
-
功能等价性
-
两者均可包含成员函数和访问控制修饰符,仅默认权限不同。
-
-
二、对象的创建与内存管理
-
内存区域分类
-
栈区
-
在函数内部定义局部对象(如
Student stu;
)。 -
在函数调用结束的时候 , 栈上分配的内存会自动释放掉,这个对象也就销毁掉了
-
-
静态区
-
全局对象或
static
修饰的局部对象。 -
程序结束时释放内存。
-
-
堆区
-
通过
new
动态分配内存(如Student *pstu = new Student;
)。 -
需手动通过
delete
释放内存,避免内存泄漏。
-
-
-
对象生命周期
内存区域 创建方式 释放时机 栈区 局部变量(如 Student stu
)函数结束自动释放 静态区 全局变量或 static
变量程序结束自动释放 堆区 new
动态分配需手动调用 delete
-
时间类的设计
-
成员变量:
hour
,minute
,second
(设为private
)。 -
公有成员函数:
-
setHour()
,setMinute()
,setSecond()
:设置时间。 -
displayTime()
:输出时间。
-
-
示例代码:
-
class Time {
private:
int hour, minute, second;
public:
void setHour(int h) { hour = h; }
void setMinute(int m) { minute = m; }
void setSecond(int s) { second = s; }
void displayTime() {
cout << hour << ":" << minute << ":" << second << endl;
}
};
-
在不同内存区域创建对象
-
栈区:
Time t;
-
静态区:
-
全局变量:
Time globalTime;
-
局部静态变量:
static Time staticTime;
-
-
堆区:
Time *pTime = new Time;
(需手动delete pTime;
)
-
三、关键概念
-
封装:通过私有成员和公有接口隐藏实现细节。
-
内存管理:区分栈、堆、静态区的生命周期,避免内存泄漏。
-
访问控制:合理使用
public
、private
、protected
控制数据安全性。
this
指针
一、this
指针
-
定义与作用
-
this
指针是一个隐式指针,指向调用成员函数的对象的首地址。 -
由编译器自动添加到每个非静态成员函数的参数列表中。
-
用途:
-
区分同名成员变量与参数:当成员函数参数与成员变量同名时,通过
this->变量名
明确访问成员变量。 -
返回对象自身引用:在链式调用中,可通过
return *this
返回当前对象。
-
-
-
示例
void Student::setName(const string &name) { this->name = name; // 使用this指针区分成员变量和参数 }
二、构造函数
-
定义与特点
-
定义:构造函数是类的一个特殊成员函数,用于初始化对象的数据成员。
-
特点:
-
函数名与类名相同,无返回值(包括
void
)。 -
对象创建时自动调用,生命周期内仅调用一次。
-
支持重载(如默认构造函数、带参数构造函数)。
-
不能是虚函数(虚函数依赖虚表指针,构造时虚表未初始化)。
-
不能用
const
修饰(构造函数用于初始化,修改对象状态是必要的)。
-
-
-
构造函数的分类
-
默认构造函数:无参数,若未手动定义,编译器自动生成。
-
带参数构造函数:用于初始化成员变量。
-
拷贝构造函数(未提及但相关):用于对象拷贝。
-
-
成员变量初始化方式
-
函数体内赋值:
-
Student::Student(const string &name, int age) {
this->name = name; // 先默认构造,再赋值
this->age = age;
}
初始化列表(更高效):
Student::Student(const string &name, int age) : name(name), age(age) {}
- 优势:直接调用成员变量的构造函数,避免默认构造+赋值的额外开销。
-
注意事项
-
若用户自定义了构造函数,编译器不再生成默认构造函数。
-
初始化列表的成员顺序应与类中声明顺序一致,避免依赖问题。
-
三、实际应用示例:字符串类的设计
-
类定义
class String {
public:
String(const char *str = nullptr); // 构造函数参数为const char*
void show(); // 输出字符及ASCII码
private:
char *str; // 动态分配的字符串
};
构造函数实现
String::String(const char *str) {
if (str) {
this->str = new char[strlen(str) + 1];
strcpy(this->str, str);
} else {
this->str = nullptr;
}
}
完整代码:
#include <iostream>
#include <cstring> // 需要包含头文件以使用 strlen 和 strcpy
using namespace std;
class String
{
private:
char* str;
public:
//构造函数实现
String(const char* str);
//非构造函数,需要定义类型
void show();
~String();
};
String::String(const char* in_str)
{
if(in_str!=0){
//为防止无法进行传值
str = new char [strlen(in_str)+1];
strcpy(str,in_str);
}else{
str = nullptr;
}
}
void String::show()
{
//每次上来先进行判断
if(str!=nullptr){
//用是否到最后\0进行判断
for(int i =0 ;str[i] != '\0';i++){
cout << "字符: " << str[i] << ", ASCII码: " << static_cast<int>(str[i]) << endl;
}
}
int main() {
String s1("Hello World!"); // 正确:深拷贝字符串字面量
s1.show();
return 0;
}
-
关键问题
-
字符串常量处理:字符串字面量类型为
const char[]
,赋值给非const
的char*
会报错。需使用const char*
参数,并在内部动态分配内存(运用到深拷贝,浅拷贝只会将地址传输)。 -
内存管理:需在析构函数中释放动态分配的内存,避免内存泄漏。
-
四、总结对比
特性 | this 指针 | 构造函数 |
---|---|---|
作用 | 访问当前对象的成员,解决同名冲突 | 初始化对象成员,保证对象有效状态 |
隐式/显式 | 隐式存在,显式使用this-> | 显式定义,自动调用 |
生命周期 | 与成员函数调用周期一致 | 对象创建时调用,仅一次 |
语法限制 | 仅非静态成员函数可用 | 无返回值,不能是虚函数 |
五、关键概念
-
封装性:通过构造函数确保对象初始化的合法性。
-
效率优化:优先使用初始化列表提升性能。
-
内存安全:动态资源管理需结合构造函数和析构函数(RAII原则)。
析构函数
一、析构函数
-
定义与作用
-
析构函数是类的特殊成员函数,用于释放对象占用的资源(如动态内存)。
-
函数名为
~类名
,无参数、无返回值,不能重载。 -
对象生命周期结束时自动调用(如栈对象离开作用域、堆对象被
delete
)。
-
-
实现示例
class String {
private:
char *str;
public:
String(const char *str = nullptr);
~String(); // 析构函数声明
};
String::~String() {
if (this->str) {
delete[] this->str; // 释放动态内存
}
}
-
常见错误与修复
-
内存泄漏:未在析构函数中释放动态内存。
-
重复释放(double free):多个对象共享同一内存(浅拷贝导致)。
-
修复方法:在析构函数中正确释放资源,并通过深拷贝避免共享资源。
-
二、拷贝构造函数
-
定义与作用
-
拷贝构造函数用于通过已有对象初始化新对象,函数签名为
类名(const 类名 &obj必须使用引用)
。 -
默认提供浅拷贝:直接复制成员值,可能导致资源重复释放。
-
必须自定义深拷贝:当类包含动态资源(如指针)时,需手动分配内存并复制内容。
-
-
浅拷贝 vs 深拷贝
-
浅拷贝:
-
String::String(const String &obj) {
this->str = obj.str; // 共享同一内存
}
- 问题:两个对象的
str
指向同一内存,析构时触发double free
。 -
深拷贝:
String::String(const String &obj) {
if (obj.str) {
int len = strlen(obj.str);
this->str = new char[len + 1];
strcpy(this->str, obj.str); // 独立分配内存并复制内容
}
}
- 优势:每个对象拥有独立资源,避免重复释放。
-
拷贝构造函数调用时机
-
对象初始化:
String str2 = str1;
-
函数传参:按值传递对象时触发拷贝构造(传递引用或指针不触发)。
-
函数返回对象:返回局部对象时可能触发拷贝构造(受编译器优化影响)。
-
-
编译器优化与调试
-
使用
-fno-elide-constructors
编译选项可禁用优化,观察拷贝构造函数的实际调用次数。
-
三、关键错误分析与修复
-
示例代码错误
class Test { public: Test(const Test obj) { // 错误:参数应为引用 `const Test &obj` *this = obj; // 浅拷贝导致重复释放 } };
-
错误点:
-
拷贝构造函数参数未使用引用,导致递归调用(传值触发无限拷贝)。
-
浅拷贝导致多个对象共享
data
指针,析构时重复释放。
-
-
修复方法:
Test::Test(const Test &obj) { // 使用引用 data = new int[size]; // 假设 size 有效 memcpy(data, obj.data, size * sizeof(int)); // 深拷贝 }
-
-
析构函数实现问题
~Test() { delete data; // 错误:未使用 `delete[]` }
-
修复方法:
~Test() { delete[] data; // 正确释放数组内存 }
-
最终代码:
#include <iostream>
#include <string.h>
using namespace std;
class Test
{
public:
// 一般情况下最好使用初始化列表(更高效)
Test(int size) : size(size)
{
// 直接赋值的情况下使用
cout << "Test(int size)" << endl;
data = new int[size];
}
//使用拷贝构造函数时必须传引用
Test(const Test &obj)
{
cout << "Test(const Test obj)" << endl;
// 使用浅拷贝会导致double析构
this->size = obj.size;
this->data = new int(obj.size);
for (int i = 0; i < this->size; i++)
{
this->data[i] = obj.data[i];
}
}
~Test(void)
{
cout << "~Test()" << endl;
// 数组必须要这样进行处理
delete[] data;
}
private:
int size;
int *data;
};
// 传参,用现有对象创建新的对象,返回共有3个拷贝构造函数
Test function(Test obj)
{
Test tmp = obj;
return tmp;
}
int main(void)
{
Test obj1(3);
Test obj2 = function(obj1);
return 0;
}
四、总结对比
特性 | 析构函数 | 拷贝构造函数 |
---|---|---|
作用 | 释放资源 | 初始化新对象 |
默认行为 | 释放成员变量(不处理动态资源) | 浅拷贝(直接复制成员值) |
自定义必要性 | 需手动释放动态资源 | 需自定义深拷贝避免共享资源 |
语法要求 | 无参数,不可重载 | 参数必须为引用 const & |
五、核心原则
-
RAII(资源获取即初始化):通过构造函数获取资源,析构函数释放资源。
-
深拷贝管理动态内存:避免浅拷贝导致的重复释放。
-
引用传递对象:减少拷贝构造函数调用,提升性能。
#include <iostream>
#include <string.h>
using namespace std;
class student
{
private:
string time;
int hour;
int min;
int sed;
public:
void Gethour(const int hour);
// int Sethour();
void Getmin(const int min);
// int Setmin();
void Getsed(const int sed);
// int Setsed();
void show_time(const string &time);
};
void student::Gethour(const int _hour)
{
hour = _hour;
}
void student::Getmin(const int _min)
{
min = _min;
}
void student::Getsed(const int _sed)
{
sed = _sed;
}
void student::show_time(const string &time)
{
cout << "hour:" << hour << "mins" << min << "sed" << sed << endl;
}
int main()
{
student stu;
stu.Gethour(12);
stu.Getmin(23);
stu.Getsed(33);
stu.show_time("show_time");
return 0;
}
static
关键字
一、static
关键字在C++类中的核心作用
static
关键字在C++类中用于定义静态成员变量和静态成员函数,其核心特性如下:
一、static
关键字的用途
static
在C++中用于修饰类的成员变量和成员函数,实现以下功能:
-
共享数据:静态成员变量被所有类对象共享,用于统计全局状态(如内存使用量)。
-
独立于对象:静态成员函数不依赖对象实例,可通过类名直接调用。
-
单例模式:通过私有构造函数和静态成员函数控制对象的唯一性。
二、静态成员变量
-
定义与初始化
-
声明:在类内用
static
修饰成员变量(如static int memorySpaceSize;
)。 -
初始化:必须在类外单独初始化(如
int String::memorySpaceSize = 0;
)。
-
-
特性
-
共享性:所有对象共享同一份静态变量。
-
生命周期:与程序生命周期一致,不依赖对象存在。
-
访问方式:通过类名(
String::memorySpaceSize
)或对象访问。
-
-
应用场景
-
统计类对象的全局资源使用(如堆内存消耗)。
-
实现单例模式中的唯一实例存储。
-
三、静态成员函数
-
定义与调用
-
声明:在类内用
static
修饰成员函数(如static void showMemorySpaceSize();
)。 -
调用:通过类名直接调用(
String::showMemorySpaceSize()
)。
-
-
特性
-
无
this
指针:无法直接访问非静态成员变量或函数。 -
工具性:适合实现与对象无关的功能(如打印全局状态)。
-
-
应用场景
-
操作静态成员变量(如
showMemorySpaceSize
)。 -
提供全局工具函数(如数学计算、日志记录)。
-
四、单例模式实现
通过static
关键字实现仅允许创建一个对象的类:
class Singleton {
private:
static Singleton* instance; // 静态成员保存唯一实例
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
if (!instance) {
instance = new Singleton(); // 唯一实例创建
}
return instance;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
关键点:
-
构造函数私有化,禁止外部直接创建对象。
-
通过静态成员函数
getInstance()
控制实例的创建与访问。
五、示例代码分析与改进
-
内存统计示例(
String
类)-
静态变量:
memorySpaceSize
统计所有String
对象的总内存消耗。 -
构造函数:分配内存时累加
memorySpaceSize
。 -
析构函数:释放内存时减少
memorySpaceSize
。 -
问题修复:
-
深拷贝:在拷贝构造函数中重新分配内存,避免浅拷贝导致的双重释放(
double free
)。 -
内存泄漏:确保每个
new[]
对应delete[]
。
-
-
-
单例模式代码改进
-
线程安全:多线程环境下需加锁(如
std::mutex
)。 -
资源释放:添加
deleteInstance()
方法手动释放唯一实例。
-
六、关键对比
特性 | 静态成员变量 | 静态成员函数 |
---|---|---|
归属 | 类所有,对象共享 | 类所有,与对象无关 |
初始化 | 类外初始化 | 无需特殊初始化 |
访问非静态成员 | 不可直接访问 | 不可直接访问(静态成员函数不能直接访问非静态成员变量、函数) |
调用方式 | 类名::变量名 或 对象.变量名 | 类名::函数名() 或 对象.函数名() |
七、总结
-
static
的核心作用:实现数据共享、功能独立于对象、控制对象创建。 -
内存管理:静态成员变量需谨慎管理,避免内存泄漏或数据竞争。
-
设计模式:通过
static
实现单例模式,确保全局唯一性。 -
代码规范:优先使用初始化列表、深拷贝和边界检查,提升代码健壮性。
设计一个类, 只允许这个类在外部创建一个对象 ,提示思路如下:
把 构造函数私有化 ,外面就无法创建对象了在类内部提供一个 静态函数创建对象
#include <iostream>
using namespace std;
class Str
{
private:
// 把构造函数私有化
Str() {};
// 禁止拷贝和创建新的对象
Str(const Str &) = delete; // 创建新的
Str &operator=(const Str &) = delete; // 只能是定义一个operator去操作
public:
// 静态变量函数成员获取数据
static Str &getdata()
{
// 定义局部变量
static Str str;
return str;
}
void print_data(const string &msg)
{
cout << "Printing data: " << msg << endl;
}
};
int main()
{
Str &obj1 = Str::getdata();
obj1.print_data("第一次");
Str &obj2 = Str::getdata();
obj2.print_data("第二次");
cout << "obj1地址: " << &obj1 << endl;
cout << "obj2地址: " << &obj2 << endl;
return 0;
}
- 这是本人的学习笔记不是获利的工具,小作者会一直写下去,希望大家能多多监督我
- 文章会每攒够两篇进行更新发布(受平台原因,也是希望能让更多的人看见)
- 感谢各位的阅读希望我的文章会对诸君有所帮助