public、protected、private
定义一个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; }常用方法:
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";注意: 字符串连接,只能用于“字符串+变量”,不能“字符串+字符串”
new/delete 是C++的运算符;类似于malloc/free,程序运行(动态)得开辟内存空间(堆);
new 可以为内置类型的变量开辟空间,数组变量,类的对象开辟空间。
这是在堆上开辟内存,返回一个指向该内存空间的地址。
new/delete会调用类的构造函数和析构函数(malloc不会)。
注意:类内定义的函数优先选择编译为内联函数(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(){}构造函数一定是没有返回值的。在参数上,如果没有参数,我们都称之其为默认构造函数。
如果是默认构造函数(不带参数的构造函数),在实例化的时候,有如下几种写法:
// 在堆中创建 Coordinate *p1 = new Coordinate(); Coordinate *p2 = new Coordinate; // 在栈中创建 Coordinate p2;如果在堆中进行创建,那我们也需要手动进行释放,代码如下:
delete p1; p1 = nullptr;// 不是必要,只是防止delete失败,所以这里再将指针置为安全状态 // 如果是类数组 delete[] p; p1 = nullptr;初始化列表是连接到构造函数名后面的,初始化列表和构造函数一样能够初始化类中的属性,但是对于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将有一个不可预测的值。
有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员;第二个是,如果你决定使用初始化列表,总是按照它们 声明的顺序 罗列这些成员。这将有助于消除混淆。
转载自:必须在构造函数初始化式里进行初始化的数据成员有哪些?
格式:类名(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 关键字只能放在函数声明的尾部,看起来确实是怪怪的。
格式:~类名(),不能加任何参数。
成员函数除了析构函数之外,都可以有重载。
本篇为视频教程笔记,视频如下:
C++远征之封装篇(上)