day3
学习笔记
类和对象
-
面向对象编程思想
- 任何一个事物可看成一个对象
- 对象两要素:属性和行为
-
面向对象三要素
- 封装
- 继承
- 多态
-
类的声明
#include <iostream> // 演示类的最基本使用方式,和struct一致 struct DateStruct { int year; int month; int day; }; class DateClass { public: int year; int month; int day; }; // 演示类的成员函数 class Date { public: int year; int month; int day; void print() { std::cout << year << "/" << month << "/" << day; } void printTwice() { // 成员函数可以相互调用 print(); print(); } }; // 以下演示类的访问权限 // 以上面的DateClass为例说明struct/class默认权限不同,class需显式指定public // 通过public接口访问私有成员 class Time { public: int getHour() const { return hour;//方便打断点调试(一个就好) } int getMinute() const { return minute; } int getSecond() const { return second; } private://外部不能直接修改 int hour; int minute; int second; }; // 权限控制粒度为类,而非对象 class TimeWithCopy { public: void copyFrom(const TimeWithCopy& src)//同类型对象能访问其private成员 { hour = src.hour; minute = src.minute; second = src.second; } private: int hour; int minute; int second; }; // 探讨public/private给我们带来了什么好处? // 1. 类的内部数据得到保护 // 2. 类的使用方法更明确,不易出错 // 3. 类的实现细节更容易修改 // 4. 内部数据修改有了统一入口,更容易调试 int main() { { DateClass date = { 2021, 4, 9 }; std::cout << date.year << "/" << date.month << "/" << date.day << std::endl; date.day = 10; std::cout << date.year << "/" << date.month << "/" << date.day << std::endl; } { Date date = {2021, 4, 9}; date.print(); } return 0; }
-
构造函数
#include <iostream> // 类成员变量必须被显式初始化,否则处于未初始化状态,访问时会取到不可预料的值 class Uninitialized { public: int foo; int bar; }; // 有理数类,演示构造函数的用法 class Rational { public: // 默认构造函数 Rational() { numerator = 0; denominator = 1; } // 带参数的构造函数 Rational(int n, int d) { numerator = n; denominator = d != 0 ? d : 1; // 分母不能为零 } // 构造函数同样支持重载 Rational(float f) { // 浮点数转换为有理数实现较复杂,以下代码示意下 numerator = static_cast<int>(f); denominator = 1; } float getFloat() const { float n = static_cast<float>(numerator); float d = static_cast<float>(denominator); return n / d; } private: int numerator; // 分子 int denominator; // 分母 }; // 添加自定义构造函数后隐式生成的默认构造函数将被取消 class Foo { public: Foo(int f) { foo = f; } private: int foo; }; int main() { // 新版本编译器会检测到引用未初始化变量会直接报编译错误 //Uninitialized unitialized; //std::cout << unitialized.foo << " " << unitialized.bar << std::endl; // 调用默认构造函数,初始化为0 Rational zero; //Rational zero(); // 会被编译器当成函数声明 // 显式调用构造函数初始化为1/3 Rational r1(1, 3); Rational r2 = {1, 3}; // initializer list, C++11特性 // 调用重载的构造函数 Rational r3(1.0f); // 学生尝试:合并前两个构造函数 // 有明确的构造函数,默认构造函数不存在 //Foo f; return 0; }
-
构造函数初始化
#include <string> #include <vector> #include <map> // 构造函数中对成员变量赋值 class AssignmentSample { public://编码习惯:添加成员要记得对每一个构造函数添加初始化 AssignmentSample(int v1, const std::string& v2) { var1 = v1; var2 = v2; } private: int var1; std::string var2; }; // 构造函数初始化列表 class InitializationListSample { public: InitializationListSample(int v1, const std::string& v2) : var1(v1), var2(v2) { } private: int var1; std::string var2; }; // 类声明时就地初始化,推荐此种方式 class InClassSample { public: // 构造函数中不必将所有成员变量再初始一遍了 InClassSample(int v1) : var1(v1) { } InClassSample(const std::string& v2) : var2(v2) { } private: // 假设成员变量很多 int var1 = 0; std::string var2 = "foo"; bool var3 = false; float var4 = 0.0f; double var5 = 0.0; // ... ... // 不支持静态变量 //static int var6 = 0; static const int var6 = 0; }; // 自行验证三种初始化方式的优先顺序: // 1. 声明时初始化; // 2. 构造函数初始化列表; // 3. 构造函数体内赋值; int main() { // 使用initializer list初始化容器 std::vector<std::string> names = { "James", "Kevin", "Steph", "Chris" }; std::map<std::string, int> scores = { { "James", 100 }, { "Kevin", 99 }, { "Steph", 98 }, { "Chris", 97 } }; // 使用initializer list初始化类,自动对应到相应构造函数 InClassSample foo = { 1 }; InClassSample bar = { "bar" }; return 0; }
Quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/constructors-gq/
-
析构函数
#include <string.h> // 整型数组,析构函数中释放内存 class IntArray { public: IntArray(int len) { if (len > 0) { array = new int[len]; memset(array, 0, sizeof(int) * length); length = len; } } ~IntArray() { // 注意delete nullptr是合法的 delete [] array; } void setValue(int index, int value) { if (index < length) array[index] = value; } int getValue(int index) const { if (index < length) return array[index]; else return 0; } int getLength() const { return length; } private: int* array = nullptr; int length = 0; }; // 程序退出或模块卸载时析构 IntArray baz(20); int main() { // 栈中释放时析构 IntArray foo(10); // delete时析构 IntArray* bar = new IntArray(5); delete bar; return 0; }
Quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/destructors-gq/
-
默认生成的函数
- 默认默认构造函数
- 默认析构函数
- 拷贝构造函数
- 复制操作符
- 地址运算符
-
复制构造函数
#include <string.h> // 以深拷贝为例演示拷贝构造函数 class Copyable { public: Copyable() { var2 = new int[10]; } ~Copyable() { delete [] var2; } //浅拷贝有时候会有问题delete时(指针) // 拷贝构造函数 Copyable(const Copyable& copy) { var2 = new int[10]; memcpy(var2, copy.var2, sizeof(int) * 10); } // 赋值运算符 Copyable& operator=(const Copyable& copy) { var1 = copy.var1; delete [] var2;//记得delete原来的内存空间 var2 = new int[10]; memcpy(var2, copy.var2, sizeof(int) * 10); return *this; } private: int var1 = 0; int* var2 = nullptr; }; // 去掉默认生成的拷贝函数 class Nocopyable { public: Nocopyable(const Nocopyable& copy) = delete; Nocopyable& operator=(const Nocopyable& copy) = delete; }; int main() { { Copyable foo; // 以下两种方式均会调用拷贝构造函数 Copyable bar(foo); Copyable baz = foo; // 触发赋值运算符函数 Copyable qux; qux = foo; } { // 默认生成的拷贝构造及赋值移除后不可调用 //Nocopyable foo; //Nocopyable bar = foo; //bar = foo; } return 0; }
-
this指针
-
指向对象自身首地址
-
引用整个对象
- *this
-
仅能在类内部使用
#include <iostream> // 以查看汇编代码的方式引入this指针 class Printer { public: // 查看汇编指令看到取ecx寄存器存入this指针 void printInt(int i) { std::cout << i << std::endl; ++intCount; } void printFloat(float f) { std::cout << f << std::endl; ++floatCount; } void printDouble(double d) { std::cout << d << std::endl; ++doubleCount; } private: int intCount = 0; int floatCount = 0; int doubleCount = 0; }; // this指针的典型用法 class ThisPointer { public: ThisPointer(int a, float b, double c) { // this指针引用成员变量 this->a = a; this->b = b; this->c = c; // this指针引用成员函数 this->init(); } private: void init() { std::cout << __FUNCTION__ << std::endl; } int a; float b; double c; }; int main() { // 查看汇编代码看到函数调用时将p的指针存入ecx寄存器 Printer printer; printer.printInt(0); printer.printFloat(0.1f); printer.printDouble(0.01); // 以上成员函数的调用等价于以下普通函数调用 //printInt(&p, 0); return 0; }
Quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/this-pointer-gq/
-
-
static成员
#include <iostream> // 以uid生成为例演示static成员 class User { public: User() { uid = ++total; } int getUserId() const { return uid; } static int getTotalUserCount() { // 静态成员函数不含this指针,无法访问成员变量 //std::cout << uid << std::endl; return total; } private: int uid; static int total; // 非const静态成员变量无法在类定义中初始化 //static int total = 0; static const int foo = 0;//const可以直接在里面初始化,否则只能在外面初始化 }; // 静态成员变量初始化 int User::total = 0; int main() { User usr1; std::cout << "new user with uid #" << usr1.getUserId() << std::endl; User usr2; std::cout << "new user with uid #" << usr2.getUserId() << std::endl; // 建议通过类名引用静态成员函数 std::cout << "there are " << User::getTotalUserCount() << " user(s) totally" << std::endl; // 通过对象也可以引用静态成员函数 std::cout << usr1.getTotalUserCount() << usr2.getTotalUserCount() << std::endl; return 0; }
Quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/static-keyword-gq/
-
const成员函数:表示该函数不会修改成员
#include <iostream> class Foo { public: // const成员函数 int getBar() const { // 不可调用非const方法 //setBar(1); return bar; } // 非const成员函数 void setBar(int v) { bar = v; } private: int bar = 0; public: int baz = 0; }; void tryConstFoo(const Foo& foo) { // 不能调用非const方法或直接修改成员变量 //foo.setBar(1); //foo.baz = 1; } void tryConstFoo(const Foo* foo) { // 不能调用非const方法或直接修改成员变量 //foo->setBar(1); //foo->baz = 1; // const_cast转换为非const指针 Foo* casted = const_cast<Foo*>(foo); casted->setBar(1); casted->baz = 1; } int main() { // 不修改对象的前提下,可以访问类成员变量和调用const成员函数 const Foo foo; std::cout << foo.getBar() << std::endl; std::cout << foo.baz << std::endl; // 不能调用非const方法或直接修改成员变量 //foo.setBar(1); //foo.baz = 1; // const引用 tryConstFoo(foo); return 0; }
Quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/const-keyword-gq/
-
友元:声明之后可以访问私有成员和方法
- 友员也破环了类的隐藏与封装
- 友元关系不能被继承
- 友元关系是单向的,不具有交换性
- 友元关系不具有传递性
#include <iostream> class Temperature; // 友元成员函数所属类的定义必须放在友元声明之前 class TemperatureTrainee { public: float queryTemperature(const Temperature& temp); void accessTemperature(const Temperature& temp); }; // 演示三种友元的使用方式 class Temperature { public: // 传入摄氏度 Temperature(float temp) { fTemp = temp * 1.8f + 32.0f; } // 返回华氏度 float getTemparature() const { return (fTemp - 32.0f) / 1.8f; } private: // 内部存储为华氏度,不对外暴露 float fTemp; // 友元函数示例 friend void printFahrenheitTemperature(const Temperature& temp); // 友元类 friend class TemperatureMaster; // 友元成员函数 friend float TemperatureTrainee::queryTemperature(const Temperature& temp); }; // 友元函数可访问Temperature私有成员 void printFahrenheitTemperature(const Temperature& temp) { std::cout << temp.fTemp << std::endl; } // 友元类可访问Temperature私有成员 class TemperatureMaster { public: void increateTemperature(Temperature& temp) { temp.fTemp += 1.0f; } void decreaseTemperature(Temperature& temp) { temp.fTemp += 1.0f; } }; // 友元成员函数可访问Temperature私有成员 float TemperatureTrainee::queryTemperature(const Temperature& temp) { return temp.fTemp; } void TemperatureTrainee::accessTemperature(const Temperature& temp) { // 非友元成员函数无法访问 //temp.fTemp; } int main() { Temperature temp(19.2f); printFahrenheitTemperature(temp); TemperatureMaster master; master.increateTemperature(temp); return 0; }
Quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/friend-function-and-class-gq/
-
more quiz:https://www.geeksforgeeks.org/c-plus-plus-gq/class-and-object-gq
-
end
总体情况
学习了课上讲的类的相关知识及实用细节,认真体会quiz中的题目,在写task的时候体会了面向对象的编程方法,编码中出现一些问题能够查资料解决。遇到的部分问题:ifstream传参时必须用引用,string类转数字使用stoi函数等等。task完成了数据结构设计与存储,剩余工作为查询部分,同时使用了学习的知识代码复用,简化编码。