C++面向对象笔记(2):封装篇(上)

    xiaoxiao2022-07-12  141

    文章目录

    1.类的三种访问限定符2.实例化类的对象与访问对象成员3.String字符串的使用4.new和delete运算符-内存的创建与释放5.在类的外部定义类函数6.内存区块与代码之间的联系7.构造函数8.构造函数-初始化列表9.拷贝构造函数10.析构函数

    1.类的三种访问限定符

    public、protected、private

    2.实例化类的对象与访问对象成员

    定义一个TV类:

    class TV{ public: char name[20]; int type; void changeVol(); void power(); }

    从栈中实例化:

    int main(void){ TV tv; TV tv[20]; return 0; }

    从堆中实例化:

    int main(void){ TV *p = new TV(); TV *q = new TV[20]; // todo(如果从堆中,一定要手动释放) delete p; delete []q; p = nullptr; q = nullptr; return 0; }

    注意:要区分是栈还是堆也很简单,堆我们需要手动分配、释放内存(new、delete)。

    访问对象成员(栈):

    int main(void){ TV tv; tv.type = 0; return 0; }

    访问对象成员(堆):

    int main(void){ TV *p = new TV(); p->type = 0; p->changeVol(); delete p; p = nullptr; return 0; }

    访问对象成员(数组):

    int main(void){ TV *p = new TV[5]; for(int i = 0; i < 5; i++){ p[i]->type = 0; p[i]->changeVol(); } delete []p; p = nullptr; return 0; }

    3.String字符串的使用

    常用方法:

    s.empty() s.size() s1+s2

    字符串连接:

    string s1 = "hello"; string s2("world"); string s3 = s1 + s2; string s4 = "hello" + s2; string s5 = "hello" + s2 + "world"; // 错误 string s6 = "hello" + "world";

    注意: 字符串连接,只能用于“字符串+变量”,不能“字符串+字符串”

    4.new和delete运算符-内存的创建与释放

    new/delete 是C++的运算符;类似于malloc/free,程序运行(动态)得开辟内存空间(堆);

    new 可以为内置类型的变量开辟空间,数组变量,类的对象开辟空间。

    这是在堆上开辟内存,返回一个指向该内存空间的地址。

    new/delete会调用类的构造函数和析构函数(malloc不会)。

    5.在类的外部定义类函数

    注意:类内定义的函数优先选择编译为内联函数(inline)。

    class Car{ public: void run(); void stop(); void changeSpeed(); }; void Car::run(){} void Car::stop(){} void Car::changeSpeed(){}

    如果需要 分文件 进行定义:

    car.h

    // 存放类的声明 class Car{ public: void run(); void stop(); void changeSpeed(); };

    car.cpp

    // 存放类的定义,切记要包含(include)头文件 void Car::run(){} void Car::stop(){} void Car::changeSpeed(){}

    6.内存区块与代码之间的联系

    区栈区int x = 0; int *p = nullptr;堆区int *p = new int[20];全局区存储全局变量及静态变量常量区string str = “hello”;代码区存储逻辑代码的二进制

    7.构造函数

    构造函数一定是没有返回值的。在参数上,如果没有参数,我们都称之其为默认构造函数。

    如果是默认构造函数(不带参数的构造函数),在实例化的时候,有如下几种写法:

    // 在堆中创建 Coordinate *p1 = new Coordinate(); Coordinate *p2 = new Coordinate; // 在栈中创建 Coordinate p2;

    如果在堆中进行创建,那我们也需要手动进行释放,代码如下:

    delete p1; p1 = nullptr;// 不是必要,只是防止delete失败,所以这里再将指针置为安全状态 // 如果是类数组 delete[] p; p1 = nullptr;

    8.构造函数-初始化列表

    初始化列表是连接到构造函数名后面的,初始化列表和构造函数一样能够初始化类中的属性,但是对于const修饰的常量,只能用 初始化列表 进行初始化。即如果需要初始化,必须写到 初始化列表 当中。

    初始化列表先于构造函数执行初始化列表,只能用于构造函数初始化列表可以同时初始化多个数据成员

    例子1,对const修饰的常量赋初值:

    // 错误,采用构造函数 class Circle{ public: Circle():{m_dPi=3.14} private: const double m_dPi; }; // 正确,采用初始化列表 class Circle{ public: Circle():m_dPi(3.14){} private: const double m_dPi; };

    例2,多参数赋值:

    // ====== 类声明START ====== class Teacher{ public: // 构造函数(没有返回值),这里设定了默认参数 Teacher(string name = "Jim",int age = 1,int max = 50); // 操作类的属性 void setName(string name); string getName() const; void setAge(int age); int getAge(); private: // 属性 string m_strName; // 名字 int m_iAge; // 年龄 }; // ====== 类声明END ====== // ====== 类实现START ====== // 构造函数(在初始化列表中,初始化属性) Teacher::Teacher(string name,int age,int max):m_strName(name),m_iAge(age){ cout << "This is the constructor!" << endl; } // 操作类的属性 void Teacher::setName(string name) {m_strName = name;} string Teacher::getName() const {return m_strName;} void Teacher::setAge(int age) {m_iAge = age;} int Teacher::getAge() {return m_iAge;} // ====== 类实现END ====== int main(int argc,char *argv[]) { Teacher tea; cout << "Name:" << tea.getName() << endl; cout << "Age:" <<tea.getAge() << endl; return 0; }

    初始化列表的成员初始化顺序: C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。

    // 构造函数的声明 class CMyClass { CMyClass(int x, int y); int m_x; int m_y; }; // 构造函数的实现 CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){ }

    你可能以为上面的代码将会首先做m_y=y,然后做m_x=m_y,最后m_x、m_y有相同的值。

    但是编译器先初始化m_x,然后是m_y,因为它们是按这样的顺序 声明 的。

    结果是m_x将有一个不可预测的值。

    有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员;第二个是,如果你决定使用初始化列表,总是按照它们 声明的顺序 罗列这些成员。这将有助于消除混淆。

    转载自:必须在构造函数初始化式里进行初始化的数据成员有哪些?

    9.拷贝构造函数

    格式:类名(const类名&变量名),变量名可写可不写。

    拷贝构造函数和构造函数一样,也可以在函数名后面添加初始化列表,如果没有去实现它,系统也会自动生成一个默认的。

    拷贝构造函数的参数是确定的,不能重载。

    在下面的3中情况下,系统自动调用 拷贝构造函数 ,此时不会调用 构造函数 !

    一个对象作为函数参数,以值传递的方式传入函数体;一个对象作为函数返回值,以值传递的方式从函数返回;一个对象用于给另外一个对象进行初始化(常称为赋值初始化);

    例1,使用 赋值初始化 来创建新的对象 :

    class stu{ public: // 构造函数 stu(){ cout << "stu" << endl; } // 拷贝构造函数 stu(const stu& stu01){ cout << "stu01" << endl; } }; int main(int argc,char *argv[]){ stu stu01; // 调用构造函数 stu stu02 = stu01; // 调用拷贝构造函数 stu stu03(stu01); // 调用拷贝构造函数 return 0; } /* 输出: stu stu01 stu01 */

    这里创建了3个实例,但是实际上只有第一次调用了构造函数,之后两次调用的都是拷贝构造函数。

    例2,当类作为 函数参数 传递时,将触发拷贝构造函数:

    class stu{ public: // 构造函数 stu(){ cout << "stu" << endl; } // 拷贝构造函数 stu(const stu&){ cout << "stu01" << endl; } }; // 当作为函数参数传递时 void test(stu t){ } int main(int argc,char *argv[]){ stu stu01; // 调用构造函数 test(stu01); // 调用拷贝构造函数 return 0; } /* 输出: stu stu01 */

    例3,当对象作为 返回值 时,触发拷贝构造函数:

    // 请看笔记中的封装篇(下)中的第3章:对象指针 // 其中有相关的实例代码

    拷贝构造函数中,如何获取引用&的值、如何手写拷贝构造函数:

    在本例中,部分类的属性操作方法的后面添加const,将其变为const成员函数(常对象函数),以便我们在拷贝构造函数中使用 &引用 来调用他们获取数据。

    // ====== 类声明START ====== class Teacher{ public: // 构造函数(没有返回值),这里设定了默认参数 Teacher(string name = "Jim",int max = 50); // 拷贝构造函数 Teacher(const Teacher& tea); // 操作类的属性 void setName(string name); string getName() const; int getMax() const; private: // 属性 string m_strName; // 名字 const int m_iMax; // 最多带的学生数 }; // ====== 类声明END ====== // ====== 类实现START ====== // 构造函数(在初始化列表中,初始化属性) Teacher::Teacher(string name,int max):m_strName(name),m_iMax(max){ cout << "02" << endl; } // 拷贝构造函数(在初始化列表中,初始化属性) Teacher::Teacher(const Teacher &teaRaw):m_strName(teaRaw.getName()),m_iMax(teaRaw.getMax()) { cout << "03" << endl; cout << "teaRaw.getName() = " << teaRaw.getName() << endl; cout << "teaRaw.getMax() = " << teaRaw.getMax() << endl; } // 操作类的属性 // name void Teacher::setName(string name) {m_strName = name;} string Teacher::getName() const {return m_strName;} // max int Teacher::getMax() const {return m_iMax;} // ====== 类实现END ====== int main(int argc,char *argv[]) { // 将调用构造函数 Teacher tea; cout << "Name:" << tea.getName() << endl; cout << "Max:" << tea.getMax() << endl; // 将调用拷贝构造函数(在这一步中,将展示如何获取引用&的值) Teacher tea01 = tea; cout << "Name:" << tea01.getName() << endl; cout << "Max:" << tea01.getMax() << endl; return 0; } /* 输出: 02 Name:Jim Max:50 03 teaRaw.getName() = Jim teaRaw.getMax() = 50 Name:Jim Max:50 */

    这样我们就能够在 拷贝构造函数 中获取到tea这个对象的值了。

    这里需要再次强调,如果在类的属性由const修饰,意味着我们只能在 初始化 的时候为其赋值,其它的时刻任何赋值操作都会报错,所以我们不能在构造函数/拷贝构造函数的{ }为其赋值,只能在 初始化列表 中赋值,且之后再无法修改。

    注意: 关于const 成员函数的声明,const 关键字只能放在函数声明的尾部,看起来确实是怪怪的。

    10.析构函数

    格式:~类名(),不能加任何参数。

    成员函数除了析构函数之外,都可以有重载。

    本篇为视频教程笔记,视频如下:

    C++远征之封装篇(上)

    最新回复(0)