类和对象以及this指针:
概念
面向对象四大特性:
抽象:抽象是一种将对象的共同特征提取出来并定义成一个通用模板的过程。类的抽象是指将一个类的共同属性和行为抽象出来,定义一个通用的类模板,而不关注具体的实现细节。
封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。封装可以使得代码模块化。
优点:
确保用户代码不会无意间破坏封装对象的状态;
被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。
继承性:让某种类型对象获得另一个类型对象的属性和方法,继承可以扩展已存在的代码
多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态),多态的目的则是为了接口重用。
计算类、结构体对象内存大小:只和成员变量有关,与成员函数无关。struct
和class
之间的主要区别在于其默认的访问控制权限
多态
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
构造函数
拷贝构造函数和赋值运算符重载函数(深拷贝)
赋值:要先释放自己原来的堆内存
MyClass(const MyClass& other) {
data = new int(*other.data); // 创建新的独立对象
}
// 赋值运算符重载函数(深拷贝)
MyClass& operator=(const MyClass& other) {
if (this == &other) {
return *this;
}
delete data; // 释放原有对象的内存
data = new int(*other.data); // 创建新的独立对象
return *this;
}
拷贝构造函数为什么要传引用:
拷贝构造函数通常应该接受一个常量引用作为参数,以确保在拷贝过程中不会修改原始对象的状态。这符合了只读和不修改原始对象的设计原则。拷贝构造函数的声明如下。
引用的好处如下:
1 避免无限递归(重点):
如果拷贝构造函数的参数是按值传递而不是引用,那么在调用拷贝构造函数时,会创建一个新的对象作为参数,这会导致另一个拷贝构造函数被调用,然后又创建一个新的对象作为参数,如此循环下去,形成无限递归。通过传递引用作为参数,可以避免这种无限递归的问题。
2 避免不必要的复制:
如果拷贝构造函数的参数是按值传递,那么在调用拷贝构造函数时,会复制传入的对象的所有成员到新创建的对象中。这涉及到了额外的内存分配和数据复制的开销。而通过传递引用,只需要传递对象的引用而不是整个对象,可以避免不必要的复制操作,提高效率。
memcpy
memcpy相当于浅拷贝,只拷贝一定size的内存大小,当这块内存大小中存在指针,指向了外部堆内存时,就无法将这块内存拷贝过来,只是浅拷贝,只适用于简单的拷贝,深拷贝还是用真正的内存拷贝,如for循环
ps:strlen(str),没有计算\0,开辟空间时所以需要+1:
data_ = new char[strlen(str) + 1];
实现string
#include <bits/stdc++.h>
using namespace std;
class String
{
public:
String(const char *str = nullptr) // 普通构造函数
{
if (str != nullptr)
{
m_data = new char[strlen(str) + 1];
strcpy(this->m_data, str);
}
else
{
m_data = new char[1]; // new char;
*m_data = '\0'; // 0
}
}
String(const String &other) // 拷贝构造函数
{
m_data = new char[strlen(other.m_data) + 1]; // 深拷贝
strcpy(m_data, other.m_data);
}
~String(void) // 析构函数
{
delete[]m_data;
m_data = nullptr;
}
// String& 是为了支持连续的operator=赋值操作
String& operator=(const String &other) // 赋值重载函数
{
if (this == &other)
{
return *this; // str1
}
delete[]m_data; // 析构
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data, other.m_data);
return *this; // str1
}
private:
char *m_data; // 用于保存字符串
};
int main()
{
// 调用带const char*参数的构造函数
String str1;
String str2("hello");
String str3 = "world";
// 调用拷贝构造函数
String str4 = str3;
String str5(str3);
// 调用赋值重载函数
/*
str1 = str2
str1.operator=(str2) => str1
str3 = str1
*/
str3 = str1 = str2;
return 0;
}
ps:自定义了构造函数,编译器就不会提供默认构造函数了。
构造的初始化列表
初始化顺序是按照成员变量的定义一样,和初始化列表出现的先后顺序无关
常方法:
常方法:可以由常或非常对象调用,但常对象只能调用常方法,涉及C++基础二的const指针转换
且函数体内的成员变量不允许被修改
对于只读方法:建议一律实现成常方法,这样常对象可以调用,普通对象也可以调用
void show() const
{
}