侯捷老师经典 C++
3.3 构造函数
- 访问级别
- 构造函数——没有返回值类型
- 大气的正规的写法:利用构造函数的初始化列表,初始化时第一阶段对变量的赋值,时间和效率上都有所提高
- 构造函数可以有很多个——overloading(重载)
4.4 参数传递和返回值
-
把构造函数放到 private 里面——需要在public 中调用singleton单例模式
-
常成员函数——在函数的后面加上const,花括号的前面,class里面的操作有会改变数据和不会改变数据两种,
//complex类的一个成员函数,public 权限 double real() const {retrun re;}
表示不会改,只是拿这个数据,要加就一定要加,有时候不加是会出事情的
//加入在类外定义一个常对象,使用者说我这个c1是不能变得哦 const complex c1(2,1); cout << c1.real() <<endl;
这样,如果之前成员函数的定义没有写 const 会导致编译器说不行。
-
传值(pass by value)还是 传引用(pass by reference)——再过去的C语言可以传指针,但是C++有引用,那么的快,形式又漂亮。最好所有的参数都传引用,如果我传的时候不想你去改我的内容(因为人家毕竟是双向传递),我就可以设成传递常引用
complex& oprerator += (const complex& );
你只要一改,我就出错。
-
返回值(return by value)还是返回引用(return by reference)(影响效率的细节)
-
友元(friend)朋友打破了封装的大门
-
多年的程序员可能也会提起(知道这样写但不知道为什么)——相同 class 的各个objects互为友元
-
老司机看类body代码(数据在 private、传引用(要不要加const看情况)、返回值尽量用reference、在类的中能加const的地方就要加,如果不加,使用者在用的时候报错编译器会埋怨你、构造函数的 initialize list尽量用)
-
inline complex& __doap1(complex* ths,const complex& r) { ths->re +=r.re; ths->im += r.im; return *ths; } inline complex& complex::operator += (const complex& r) { return __doap1(this,r); }
在函数里面保存一个结果时有两种,第一种是保存在一个传入的变量中,第二种是建立一个变量(但是这个变量是local 变量)保存,但是第二种不能返回引用,函数用完它的本体都死亡了,你再传出去,回来看这个空间只会看到一些坏的东西不好的东西。
5.5 操作符重载与临时对象
-
head 头文件中的防卫式说明
-
操作符重载定义在成员函数,对于双目运算符的重载在参数表中只能写一个参数,第一个this省略但不能写。
-
操作符重载在非成员函数,双目运算符的形参列表要写两个参数。——全局函数
-
return by reference 语法分析——传递者无需知道接受者是以reference形式接收。
-
class body之外的各种定义
-
temp object 临时对象
inline complex operator + (const complex& x, const complex& y) { return complex(real(x) + real(y), imag(x) + imag(y)); } inline complex operator + (const complex& x, double y) { return complex(real(x) + y, imag(x)); } inline complex operator + (double x, const complex& y) { return complex(x + real(y),imag(y)); }
-
对 “ << ” 的重载,标准库的对象作为参数时,只能写成非成员函数的形式。在这里不可以加const ,因为在这里每一次输出都在改这个对象的状态
#include <iostream.h> ostream& operator << ( ostream& os,const complex& x) { return os << "(" << real(x) << "," << imag(x) << ")"; }
6.6 复习complex 类的实现过程
//complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
class complex
{
public:
complex(double r = 0,double i = 0): re (r), im (i)
{ }
complex& operator += (const complex&);
double real() const {return re}
double imag() const {return im}
private:
double re,im;
friend complex& __doap1(complex*,const complex&);
};
#endif
inline complex&
__doap1(complex* ths,const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator +=(const complex& r)
{
return __doap1(this,r);
}
inline complex
operator + (const complex& x,const complex& y)
{
return complex (real(x) + real(y),
imag(x) + imag(y));
}
inline complex
operator + (const complex& x,double y)
{
return(real(x)+y,imag(x));
}
inline complex
operator + (double x,const complex& y)
{
return (x+real(y),imag(y));
}
#include<iostream.h>
ostream&
operator << (otream& os,const complex& x)
{
return os << "(" << x.real() << "," <<x.imag() << ")" ;
}
7.7 三大函数:拷贝构造、拷贝赋值、析构
- 浅拷贝与深拷贝
- 拷贝赋值,先杀掉自己,再给自己分配空间,数据的赋值,返回*this,这个过程里先检测是否自我赋值
8.8 栈与内存管理
-
output 函数
-
所谓的 stack(栈),所谓的 heap(堆),堆是由操作系统提供的一块global的内存空间
-
stack objects 的生命期,static object 的生命在scope结束后依然存在,知道程序结束。
-
global objects 的生命期
-
内存泄漏——动态分配的内存的指针死亡了,但是那块动态分配的内存仍然存在,所以造成内存泄漏
-
new的过程:先调用 malloc 分配内存,再调用构造函数,它的反作用是delete ,delete过程是首先调用析构函数,再释放(free())内存
-
动态分配所得的内存块(memory block),in VC,array new如果不搭配 array delete 来使用的话,会导致原本需要调用n次析构函数只调用了一次,内存泄漏的是没有调用析构的地方(与类中有无动态分配的内存还有关系)。万无一失的做法就是只要有array new 就要有 array delete
9.9 复习 string 类的实现过程
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;
}
inline
String::String(const char* cstr = 0)
{
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(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1];
strcpy(m_data,str.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;
}
10.10 扩展补充:类模板、函数模板及其他
-
static ——在一个class里面使用 static ,它存在一个单独的区域,郑莉之前说哦过:属于类的,不是属于某一个对象,static 函数处理static 数据,如下代码所示:调用静态函数有两种方式(1)通过object调用,(2)通过class name调用,之前郑莉说尽量用class name 来调用
class Account { public: static double m_rate; static void set_rate(const double& x){m_rate = x;} }; double Account::m_rate = 8.0; int main() { Account::set_rate(5.0); Account a; a.set_rate(7.0); }
-
进一步补充,将构造函数放在 private 里面,在这一种单例设计模式(Singleton)
//Singleton class A { public: static A& getInstance(return a;); setup(){...} private: A(); A(const A& rhs); static A a; ... };
-
cout 的重载补充介绍(简略)
-
class template 类模板,有人说模板会造成代码的膨胀,但是这种膨胀是必要的,function template ,函数模板,它可以不用进行T 的指定,因为编译器会对 function template 进行引数推导
-
进一步补充:namespace ,在使用命名空间几种用法
- using namespace std
- using std::cout
- std::cout <<…
11.11 组合与继承
- Composition(复合),我中有你,要构造这个对象,先把里面的对象构造好,构造由内而外,析构由外而内。郑莉之前有实例说明(书上)
- Delegation(委托),有一个指针指向其他的类,也有个说法叫 Composition by reference,不同步,这种写法现在非常有名叫做 pimpl(指针应用实现)也是Handle/Body(pimpl)。这种手法又叫编译防火墙。
- Inheritance(继承),最有价值的一点就是核虚函数搭配,构造的时候由内而外,析构由外而内。一个良好的编程习惯,只要你的class 马上要变成一个父类,或者将来会可能变成父类,就把你的析构函数设置成 virtual
12.12 虚函数与多态
- Inheritance(继承) with virtual function(虚函数),继承中函数的继承是继承调用权,成员函数可以分为(1)non-virtual 函数:你不希望 derived class 重新定义(override,覆写),(2)virtual函数:你希望derived class 重新定义它(3)pure virtual函数(纯虚函数):你希望 derived class 一定要重新定义,你对他没有默认
- 通过子类的对象调用父类的函数,如果在这个父类的函数中含有子类所定义的虚函数,那么会去之类执行那个虚函数再回父类继续执行之前的父类函数,最后返回,这种做法如此的经典以至于有个专门的名称叫做 Tem-plate Method(大名鼎鼎的 23 个设计模式之一)——虚函数里面的经典用法
- Inheritance + composition 关系下的构造和析构,子类的对象里面既包含父类的东西有包含组合类的东西,它的构造过程谁先?
- Delegation(委托) + Inheritance(继承),最强大
13.13 委托相关设计
-
23个设计模式中的 Composite 模式
-
Prototype模式
更多细节需要探讨兼谈对象模型(新手们,享受下部)革命尚未成功,同志仍需努力
1.1 导读
2.2 conversion function ,转换函数
-
一个类的对象可不可以转成其他类型,即转出去的问题
class Fraction { public: Fraction(int num, int den=1): m_numerator(num),m_denominator(den){} //转换函数 operator double() const { return (double)(m_numerator / m_denominator); } private: int m_numerator; //分子 int m_denominator; //分母 }; Fraction f(3,5); double d = 4 + f; //调用operator double()将 f 转为0.6
3.3 non-explicit-one-argument ctor
-
对于含有 explicit 相当于告诉编译器不要那么的自动去转化,这种关键字用的地方比较少,一般在构造函数照片那个
//这里的bool是模板的偏特化 class Fraction { public: explicit Fraction (int num,int den =1):m_numerator(num),m_denominator(den){} operator double() const { return (double) (m_numerator / m_deniminator); } Fracyion operator+(const Fraction& f) { retrun Fraction(...); } private: int m_numerator; int m_denominator; } Fraction f(5,3); Fraction d2 = f + 4;//[error]conversion from 'double' to'Fraction'
-
他在模板上的一个例子如下(转换函数的转进来):
template<class Alloc> class vector<bool, Alloc> { public: typedef __bit_reference reference; protected: reference operator[] (size_type n) { return *(begin() + difference_type(n)); } }; //其中必然有一个转换函数将reference 转换为 bool 其中: struct __bit_reference { unsigned int* p; unsigned int mask; public: operator bool() const {return !(!(*p & mask))}; }
4.4 point-like classes,关于智能指针
-
通俗的说就是这个类所产生的对象像一个指针,把它叫做智能指针,智能指针要包含一般指针的作用
template<class T> class shared_ptr { public: T& operator*() const { return *px; } T& operator->() const { return px; } shared_ptr(T* p) : px(p){} private: T* px; long* pn; };
-
迭代器,就是主要用来遍历容器的作用——也是智能指针
5.5 function-like classes,所谓仿函数
- 类模仿函数,函数对象
- 告诉各位,标准库里面有好多仿函数,这些仿函数都有继承一些奇怪的父类。继承这些有什么用,在C++标准库课程才会提到它。
6.6 namespace 经验谈
- namespace 防止冲突
- 测试的时候可以设置自己的namespace
7.7 class template,类模板
8.8 function template, 函数模板
9.9 Member template, 成员模板
- 模板里面的模板
class Base1{};
class Derived1:public Base1{ };
class Base2{};
class Derived2:public Base2{ };
pair<Derived1, Derived2> p;
pair<Bae1, Base2> p2(p);
pair<Base1, Base2> p2(pair<Derived1, Derived2>());
-
所以模板主要分为三大类:class、function、member
-
up cast(向上转型) 指的是对于一个指向基类的指针,但是它指向一个它的子类的对象时,那么这样就称之为 up-cast
10.10 specialization,模板特化
- 特化与泛化是相对应的
11.11 partial specialization,模板偏特化
- 个数上的偏
- 范围上的偏——指向任何类型的指针
12.12 template template parameter,模板模板参数
-
其实已经很少文档在讲这个东西,这已经很高深了,但是很简单务必搞明白的哦
-
模板两个 typename ,其中有一个又是一个模板
template <typename T, template<typename T> class Container > class XCLs{...}; XCLs<string , list> mylst1;//这么写是不行的,因为容器需要好几个模板参数等,但是这一行上面的语法是没有问题的(非战之罪)
13.13 关于C++标准库
- 建议去用标准库,不用自己去写那些功能,没人家写得好
- 下一节开始就是选一些C++11的几个点来讲
14.14 三个主题
-
variadic templates(since C++11) 意味模板参数可变化,也表示的是数量不定的模板参数
void print() { } template<typename T,typename... Types> void print(const T& firstArg,const Types&... args) { cout << firstArg <<endl; print(args...); //递归 }
-
auto——一个语法糖哈哈,使用 auto 的时候一定要编译器能帮你推出来,有人可能会说:全部用auto好了,这样就极端了
//以前要这么写 list<string> c; ... list<string>::iterator ite; ite = find(c.begin(),c.end(),target); //现在 list<string> c; ... auto ite = find(c.begin(),c.end(),target);
-
range-base for (since C++11),将coll容器里面的元素给decl里面指向statement
for (decl : coll) { statement; } //比如 for (int i :{1,2,3,5,6,7}) { cout << i << endl; } //再如 vector<double> vec; ... for(auto elem :vec) { cout << elem << endl; } for(auto& elem :vec) { elem *= 3; }
15.15 reference
- 从内存的角度来探讨值(value)、指针、和引用(reference)
- 引用其实里面有一个隐含的指针,设完就不可以变了,指针是 4 个字节,假象:引用取决于它代表的那个值是多大,他就多大(即object 和其 reference 的大小相同,地址也相同,全是假象)
- reference 实际上是一个漂亮的 point
- 两个相同的函数签名是不允许的(会导致二义性)
16.16 复合&继承关系下的构造和析构
17.17 对象模型(Object Model) vptr 和 vtbl
-
父类有虚函数,子类一定有
-
虚指针、虚表、子类覆写父类的函数、虚函数的调用——动态绑定、虚机制
动态绑定的三个条件:
- 指针调用
- up cast
- 虚函数
-
虚函数的调用的这种用法,我们叫做多态
18.18 对象模型(Object Model)关于 this
19.19 const
-
const,对于放在成员函数中时,意味着不打算改变这个class 的 data,这张表值得推敲一哈子
const object non-const object const member functions (保证不更改 data members) yes yes non-const member functions(不保证 data members 不变) no yes 当成员函数的 const 和 non-const 版本同时存在,const object 只会(只能)调用 const 版本,non-const object 只会(只能)调用 non-const 版本
20.20 对象模型(Object Model)关于 Dynamic Binding
-
主要还是对于动态绑定的过程进行汇编码的解释
-
这种动态绑定用 C 语言的机制描述是
(*(p -> vptr)[n])(p);或 (* p -> vptr[n])(p);
-
这就是对这种对象模型的虚函数的很棒的表现的解释
21.21 关于new 和 delete
22.23 重载 operator new 、operator delete、operator new[]、operator delete[] & 示例(分配释放过程演示)
24.24 重载 new、delete
-
placement new : 我们可以重载 class member operator new(),写出多个版本,前提是每一个版本的声明都必须有独特的参数列,其中,第一个参数必须是size_t,其余参数以 new 所指定的placement arguments 为初值。
-
new
- 一般的 new
- operator new
- array new
- placement new