以实例开始:从我的视角看我对这个代码中涉及知识点的疑惑吧!
首先上代码,是不是觉得很长,没关系慢慢看,会有收获的!想知道是不是比我聪明,就看看你问的问题是不是比我少吧!
cpp
class Mystring {
private:
char* str;
public:
//构造函数
Mystring(const char* ps = nullptr):str(nullptr)
{
if (nullptr != ps && *ps != '\0') {
int len = strlen(ps);
str = (char*)calloc(len+1, sizeof(char));
assert(str != NULL);
strcpy(str, ps);
}
}
//析构函数
~Mystring(){
free(str);
str = NULL;
}
//深拷贝
Mystring(const Mystring& it):str (nullptr) {
if (it.str != nullptr && *(it.str) != '\0') {
str=(char*)calloc(strlen(it.str)+1,sizeof(char));
strcpy(str, it.str);
}
}
//运算符重载
Mystring &operator=(const Mystring& it) {
if (this != &it) {
free(str);
str = nullptr;
if (it.str != nullptr && *it.str != '\0') {
str = (char*)calloc(strlen(it.str) + 1, sizeof(char));
assert(str != nullptr);
strcpy(str, it.str);
}
}
return *this;
}
//打印输出
void PrintInfo() const{
if (nullptr != str) {
cout << "str:"<<str << endl;
}
else {
cout << "Empty" << endl;
}
return;
}
};
struct Node {
int value;
};
int main() {
int* p = nullptr;
int Node::* s = nullptr;
s = &Node::value;
Mystring s1;
Mystring s4;
Mystring s2("Happy");
s1.PrintInfo();
s2.PrintInfo();
Mystring s3(s2);
s3.PrintInfo();
s4 = s2;
s4.PrintInfo();
return 0;
}
(1)Mystring类和其他类有什么区别吗?
普通类成员变量是 值类型(比如存Int类型的类),数据直接存在自己的对象里
Mystring类成员变量是 指针类型 (比如存char*类型的类**)**,真实字符串存在堆内存里,对象只存地址。
(2)为什么要区分 Private 和 Public?
class 类的默认权限是 Private ; struct 默认权限是 Public
Private是封装的核心 ,目的是为保证数据内容的合法性,不让外部乱改类内部的重要数据,
通过 public 函数间接修改 private 变量,就可以做校验。
- private:把变量锁起来,不让外面乱碰
- public 函数:给你一个合法的入口
- 在函数里加判断:保证数据永远是合法的
这就是封装 的意义:保护数据,防止被乱改,让类更安全、更健壮。
(3)构造函数?
构造函数是做什么的?
对象一创建,他就自动跑,用来初始化对象
构造函数的特点是什么?
- 函数名和类名一模一样
- 没有返回值,连 void 都不用写
- 创建对象时,构造函数自动调用
- 可以有参数(重载)
结合本代码例子讲一讲构造函数:
cpp
//构造函数
Mystring(const char* ps = nullptr):str(nullptr)
{
if (nullptr != ps && *ps != '\0') {//指针不为空和不为空串
int len = strlen(ps);
str = (char*)calloc(len+1, sizeof(char));//calloc(个数,类型字节)
//char* str = (char*)calloc(len+1, sizeof(char));//错误的
assert(str != NULL);
strcpy(str, ps);
}
}
作用:创建对象时,根据传入的字符串,在堆上开辟空间并拷贝内容
- 为什么函数的参数是const char* ps = nullptr?
(1)为什么是 const char* 类型------传入的是字符串常量
加
const是为了:
防止在构造函数里不小心修改字符串内容
能接收
const和非const的字符串符合 C++ 常量正确性规范
不写
const有些编译器会报警 / 报错(2)为什么要有
= nullptr------ 默认参数调用这个构造函数时可以不传参
总之:定义了一个构造函数,可以接收一个字符串指针,有参数传入参数,没有参数默认nullptr 作为参数
- 为什么后面要写
str(nullptr)?
把成员变量 str 初始化为空指针 nullptr
":" 后面这一堆,是在构造函数执行前,给成员变量赋初值的------成员初始化列表
为什么必须显示初始化------指针如果不初始化里面是垃圾随机值
- 为什么char* str = (char*)calloc(len+1, sizeof(char));是错误的?
char* str 定义了一个局部变量和类成员 str 重名,在{}里是str是局部变量,不是类成员
- 给char* str扩容是给局部变量扩容
- 局部变量出了 {} 就销毁了,类成员str还是nullptr
正确写法: str = (char*)calloc(len+1, sizeof(char));
这样才是给类成员扩容
**更加标准的写法:**this->str = (char*)calloc(len+1, sizeof(char));
更加明确是给此类成员扩容
- 几种扩容函数的格式?
malloc:类型* 指针=(类型*)malloc(元素个数*sizeof(元素类型));
eg.char* p=(char*)malloc(10*sizeof(char));
calloc:类型* 指针=(类型*)calloc(元素个数,sizeof(元素类型));
eg.char* p=(char*)calloc(10,sizeof(char));
realloc:类型* 指针=(类型*)realloc(旧指针,元素个数*sizeof(元素类型));
eg.char* p=(char*)realloc(s,10*sizeof(char));
new:指针=new 类型 [元素个数*sizeof(元素类型)];eg.p=new(char)[10*sizeof(char)];
(4)析构函数?
析构函数是做什么的?
对象要死的时候(离开作用域、程序结束)自动跑,用来清理内存。
析构函数的特点是什么?
- 析构函数与类名相同,但在前面加上字符'~'
- 析构函数无函数返回类型,不带有参数
- 一个类只有一个析构函数
为什么要析构?
- 对象销毁时,释放堆上的字符串内存
- 避免内存泄漏
- 如果不释放,程序一直占着内存不用
(5)为什么在Mystring类中区分深拷贝和浅拷贝?
浅拷贝:
浅拷贝做了什么?
C++默认生成的拷贝构造函数和运算符重载函数,只会做一件事情:
逐字节拷贝成员变量**------就是把指针地址复制过去,不会复制堆里字符串的内容**
最后浅拷贝的后果是什么?
cpp
Mystring s2("Happy");
Mystring s3(s2);
s2.str 和 s3.str 指向同一块堆内存:
- 一个改全改,
- 一个析构释放空间另一个变成野指针,
- 最后两个对象析构,同一个内存释放两次,程序崩溃
深拷贝:
深拷贝做了什么?
自己写拷贝构造函数和赋值重载函数:
开辟新的内存空间、将堆空间中的内容复制过去、让新对象拥有独立的内存、两个对象互不干扰
深拷贝的效果?
s3 和 s2 有各自的的堆空间:
- 给 s3.str 新开辟一块内存
- 把 s2 的字符串内容复制过去
- 两个指针指向不同内存,完全独立,析构时各自释放自己的内存,不会造成内存崩溃
结合本代码例子讲一讲深拷贝:
cpp
Mystring(const Mystring& it):str(nullptr)
{
if (it.str != nullptr && *(it.str) != '\0')
{
// 1. 开新空间
str = (char*)calloc(strlen(it.str)+1, sizeof(char));
// 2. 把内容复制过去
strcpy(str, it.str);
}
}
- 为什么是 Mystring?
因为这是 Mystring 类的拷贝构造函数,函数名必须和类名一样。
- 为什么参数 const Mystring& it 要加引用?
& 引用
- 不能用值传递
Mystring it- 因为值传递会触发拷贝构造
- 那就会无限递归 → 直接崩溃
- 所以必须用引用 &
it是被拷贝的对象
- 这句it.str != nullptr && *(it.str) != '\0'的作用是什么?
用来判断被拷贝的对象 it(也就是 s2)有有效字符串吗
(6)运算符重载函数?
运算符重载函数是做什么的?
就是给 +、=、==、[]、<< 这些符号,重新定义它在你的类里怎么干活
在Mystring中其他几种常用的几种运算符重载格式?
加号 + 字符串拼接
cpp
//字符串拼接 +
Mystring operator+(const Mystring& it) const {
Mystring temp;
int len1 = (str != nullptr) ? strlen(str) : 0;
int len2 = (it.str != nullptr) ? strlen(it.str) : 0;
temp.str = (char*)calloc(len1 + len2 + 1, sizeof(char));
if (str != nullptr) strcat(temp.str, str);
if (it.str != nullptr) strcat(temp.str, it.str);
return temp;
}
字符串比较
cpp
// 字符串比较 ==
bool operator==(const Mystring& it) const {
if (str == nullptr && it.str == nullptr) return true;
if (str == nullptr || it.str == nullptr) return false;
return strcmp(str, it.str) == 0;
}
下标返回
cpp
// 下标访问 []
char& operator[](int index) {
assert(str != nullptr);
assert(index >= 0 && index < (int)strlen(str));
return str[index];
}
结合本代码的例子讲讲运算符重载函数:
cpp
Mystring &operator=(const Mystring& it) {
//1.检查是否是自己给自己赋值
if (this != &it) {
//2.将当前空间free掉
/*delete[]str;*/
free(this->str);
//3.str置空
this->str = nullptr;
//4.it.str分配空间
if (it.str != nullptr && *it.str != '\0') {
/*char* str = new(it.str)char[sizeof(strlen(it.str)+1)];*/
this->str = (char*)calloc(strlen(it.str) + 1, sizeof(char));
//assert(str != nullptr);
//5.拷贝
strcpy(this->str, it.str);
}
}
return *this;
}
- operator=的作用?
赋值运算符重载,在主函数中可以写s3=s2
- const Mystring& it的作用?
作用:让 it 等于 = 右边的对象
& 用引用避免拷贝浪费
const 只读取不修改原变量
Mystring &返回值的作用?
作用:返回当前对象的引用 *this
目的:支持链式赋值 eg.s3=s2=s1
- 先释放旧内存,那里面的东西放在哪里?(以s3=s2为例子)
首先:free(this->str);
释放的是指针指向的旧字符串内存,不是释放指针本身!指针还在只是不指向,变成了野指针
然后:this->str=nullptr;
将指针指向空,安全
其次:this->str = (char*)calloc(strlen(it.str) + 1, sizeof(char));
申请新空间
最后:strcpy(this->str, it.str);
改变指针指向,让指针指向新空间
你真棒!看到这里啦,给自己送个花花吧!