C++面向对象高级开发课程(第二周)
1. 类中含有指针—— class with pointer member(s) ——的情况经常发生,典型的有:string 类。
2. STL中的 string 类太复杂,copy on write 等等特性。
3. 采用“防卫式头文件声明”。
4. s2 赋值给 s3。
String s3("hello"), s2; s3 = s2;
5. complier 会默认生成:拷贝构造函数 和 拷贝赋值函数(操作符重载),其执行的原理是按位依次赋值。带指针的类不适合使用默认的构造函数。
class String { public: String (const String& str); //拷贝构造函数 String& operator= (const String& str); //拷贝赋值函数 } String s3(s1); //拷贝构造 String s2 = s3; //拷贝赋值
6. String 类实现原理
class String { public: String(const char* cstr=0); private: char* m_data; //指针动态分配存储空间 } String::String(const char* cstr) { if (cstr) { m_data = new char[strlen(cstr)+1]; strcpy(m_data, cstr); } else { m_data = new char[1]; *m_data = '\0'; } }
7. 只要类中带有指针,就必须自定义拷贝构造函数和拷贝赋值函数。
8. 三个特殊函数(big three):拷贝构造、拷贝赋值、析构函数。
9. 尽量使用“常量成员函数”:
char* get_c_str() const { //do something }
10. 浅拷贝:编译器默认生成的类实例间拷贝行为,对带有指针的类来说会引发 memory leak。
深拷贝:用户定义的行为(实质是一种构造函数)。
兄弟——类的不同实例——间互为 friend(友元),可以直接取对方的私有成员。
11. 一定要在 operator= 中检查是否 self assignment,是编程经验的体现。
String& String::operator=(const String& str) { // check self assignment if (this == &str) return *this; delete[] m_data; m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); return *this; }
12. stack and heap
stack:
存在于某scope(作用域)的一块内存空间(memory space)。例如当用户调用函数,函数本身会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本体内声明的任何变量,其所使用的内存块都取自上述stack。
heap:
或谓 system heap,是指操作系统提供的一块 global 内存空间,程序可动态分配(dynamic allocated)从某中获得的若干区块。
13. stack object 对象的生命期:
class Complex {......}; ...... { Complex c1(2, 3); }
c1便是 stack object,其生命在作用域(scope)结束之际结束。这种作用域之内的 object 又称为 auto object,因为它会被“自动”清理。
14. static local object 对象的生命期:
class Complex {......}; ...... { static Complex c2(2, 3); }
c2便是static object,其生命期在作用域(scope)结束之后仍然存在,直到整个程序结束。
15. global object 对象的生命期:
class Complex {......}; ...... Complex c1(2, 3); int main() { //do sometihing return 0; }
c1便是所谓 global object,其生命期在整个程序结束之后才结束,也可以视为一种 static object,作用域是“整个程序”。
16. (对象的) 存在/消失 ≈ 构造/析构 (函数)何时被调用。
17. heap objects 对象的生命期:
class Complex {......}; ... { Complex *p = new Complex; ... delete p; }
p 所指的是 heap objects ,其生命在它被 delete 之际结束。
若当作用域结束还未 delete 则会内存泄漏(memory leak):因为当作用域结束时,指针p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到指针p,也就没机会 delete p。
18. new: 先分配memory,再调用constructor
Complex *p = new Complex(1, 2);
以上的一行代码,编译器会转化为:
Complex *pc; void* mem = operator new( sizeof( Complex ) ); // 分配内存 pc = static_cast<Complex*>(mem); // 转型 pc->Complex::Complex(1, 2); // 构造函数
- operator new——操作符“new”——是C++函数,其内部调用malloc()。
-
pc->Complex::Complex(1, 2); => Complex::Complex(pc, 1, 2); / Complex::Complex(this, 1, 2);
19. delete:先调用 de-constructor,在释放内存
Complex *ps = new String("hello world"); ... delete ps;
编译器转化为
Complex::~Complex(ps); // 析构函数 operator delete(ps); // 释放内存
-
operator delete(ps); 内部调用 free(ps)
20. array new 一定要搭配 array delete。
String *p = new String[3]; ... delete[] p;
21. 头文件.h 防卫式声明
22. 32位平台下,一根指针 4 字节。
23. return by reference 使用门槛:传递的 object 不是 local object。
24. “常量成员函数”:不修改类成员数据部分的函数。
char* get_c_str() const { // do something }
const 加在 ()与{}之间。
25. inline 关键字一定要加!全部的,无负面作用。
26. static data member 内存中只有一份,不在乎存在多少实例。
static data memeber 一定在类的声明之外设初值。
static member function 调用方式有二:(1)通过 object 调用(2)通过 class name 调用。
class Account { static void set_rate(double val); }; Account::set_rate(8.0); // 通过类名调用 Account a1; a1.set_rate(8.0); // 通过实例调用
27. Q:“声明”和“定义”的区别?
A:定义会程序获得内存。
28. singleton——把ctor放在private区
class A{ public : static A& getInstance(){ return a; } setup(); private: A(); A(const A& ths); static A a; }; // 调用示例 A::getInstance().setup();
29. Meyer`s singleton——把ctor放在private区
把静态的“自己”放在函数里。好处:只有当函数被调用时,才创建object,即使离开函数以后static object也依然存在。如果该函数没被调用过,那么连一个static object也不会存在。
class A{ public : static A& getInstance(); setup(); private: A(); A(const A& ths); static A a; }; A& A::getInstance() { static A a; return a; } // 调用示例 A::getInstance().setup();
30. Q:为什么cout可以接收各种内置类型“对象”?
A:因为标准库对“<<”做了针对各种内置类型的运算符重载
31. “template会造成代码膨胀是必要的”——侯捷
代码膨胀:模版会针对用户定义的每一种类型都生成一份代码,代码的大意相同,只是类型不同。
32. class template
template <typename T> class Complex { public: Complex(T r = 0, T i = 0) : re(r), im(i) {} private: T re, im; }; int main() { complex<double> c1(1, 2); return 0; }
33. function template
class Complex { public: bool operator< (const Complex& ths) const {...} }; template <class T> inline const T& min(const T& a, const T& b) { return b < a ? b : a; // 编译器遇到“<”会自动寻找对应类型的运算符重载 } int main() { Complex c1, c2, c3; c3 = min(c1, c2); }
对于 c3 = min(c1, c2) 不必明确指出类型,因为complier会对function template进行实参推导(argument deduction)。
34. namespace
using directive
using namespace std;
using declaration
using std::cout std::cin >> ... cout << ...
35. string.h 代码:
#ifndef __MYSTRING__ #define __MYSTRING__ class String { public: String(const char* cstr=0); String(const String& str); String& operator=(const String& str); ~String(); char* get_c_str() const { return m_data; } private: char* m_data; }; #include <cstring> inline String::String(const char* cstr) { if (cstr) { m_data = new char[strlen(cstr)+1]; strcpy(m_data, cstr); } else { m_data = new char[1]; *m_data = '\0'; } } inline String::~String() { delete[] m_data; } inline String& String::operator=(const String& str) { if (this == &str) return *this; delete[] m_data; m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); return *this; } inline String::String(const String& str) { m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); } #include <iostream> using namespace std; ostream& operator<<(ostream& os, const String& str) { os << str.get_c_str(); return os; } #endif
36. 更多细节深入
•operator type() const;
•explicit complex(…) : initialization list { }
•pointer-like object
•function-like object
•Namespace
•template specialization
•Standard Library
•variadic template (since C++11)
•move ctor (since C++11)
•Rvalue reference (since C++11)
•auto (since C++11)
•lambda (since C++11)
•range-base for loop (since C++11)
•unordered containers (Since C++)