入门:C++面向对象
头文件总体布局
Section titled “头文件总体布局”
guard(防卫式声明)
只有第一次include会include进来,第二次不会被include进来
#ifndef __Complex__#define __Complex__...
#endif定义一个复数
class complex{ public: complex(double r = 0,double i = 0): re(r) ,im(i){} ... private: double re,im;}
complex c1(0,0.5)缺点:如果想把double改为float就得重新定义一个类
===> template(模版)
template<typename T>class complex{ public: complex(T r = 0,T i = 0): re(r) ,im(i){} ... private: T re,im;}
complex<double> c1(0,0.5)complex<float> c1(0,0.5)Inline(内联)
程序员设计是否inline只是给编译器建议,编译器最终是否inline取决于函数是否复杂。
class complex{ public: complex(double r = 0,double i = 0): re(r) ,im(i){}
// 在class本体里直接定义的函数会自动inline double real() const {return re;} // inline function double img() const;// declaration private: double re,im;}
// class本体内声明了的,而本体外想要建议编译器inline需要加inline关键词inline double img(const complex& x){ return x.img();}
void real(double r){real = r;}构造函数
class complex{ public: // (1)最好写成这样,直接初始化 r和i complex(double r = 0,double i = 0): // =0为默认参数 re(r) ,im(i) // 初始列 {} // 赋值 // (2)而最好不要写成这样,先初始化, 再赋值r和i complex(double r = 0,double i = 0) {re = r; im = i};
// 在class本体里直接定义的函数会自动inline double real() const {return re;} // inline function double img() const;// declaration private: double re,im;}
{ complex c1(1,2); //1+2i complex c1; // 0 + 0i complex *p = new complex(4);}可以把构造函数放在private,表示该类对象不能被其他类创建,但是可以使用友元来创建。
应用:singleton(单例)设计模式
class A{public: static A& getInstance(); setup(){...};private: A(); A(const A& rhs); ...}
A& A::getInstance(){ static A a; return a;}
A::getInstance().setup();Overload(重载)
同名函数,不同参数列表或返回值。
能重载的原因是,编译后不同参数返回值的同名函数在编译器看来名字并不一样

class complex{ public: // 1 complex(double r = 0,double i = 0): re(r) ,im(i){} // 2 complex():re(0),im(0){} // 1、2不能同时出现,因为1有默认值
// 3 double real() const {return re;} // inline function double img() const;// declaration private: double re,im;}
// 4void real(double r){real = r;}// 3、4可以同时出现==const member function (常量成员函数)==
double real() const{ return re;}- const用在成员函数表示“不会改变类对象数据内容的成员函数”
- const int/double/complex 表示常量,不会发生变化的量
下面这种情况,const member function的const不可省略,否则编译不通过.
double real() const{ return re;}
{ const complex c(2,1); cout << c1.real();}==const 对象 只能调用 有const的成员变量==
- 类中某个函数加了const,这时候其他人定义的无论是const的对象还是非const的对象,都可以调用这个函数
- 但某个函数没有加const(即成员变量可以被修改),这时候其他人定义了一个const的对象,就无法再调用这个可以修改成员变量的函数了。
因此类设计者要应const尽const。
参数传递/返回值传递
Section titled “参数传递/返回值传递”- pass by Value
- pass by Reference
- pass by Reference Const
尽量不要pass by Value
引用在底层其实就是指针
Reference Const就是传递引用,但是函数不能对这个引用进行修改,否则编译会出错
-
return by Value
-
return by Reference
比较这两种方式
(1)
inline complex& __doapl(complex *ths, const complex &r){// doapl == do assignment plusths->re += r.re;ths->im += r.im;return *ths; // ths是个指针, *ths是指针指向的对象,返回一个对象的引用}// 此时,别人调用这个函数complex c3;c3 = __doapl(&c1, c2); // 等号前面的c3不用思索就能直接写了(2)
inline complex __doapl(complex *ths, const complex &r){// doapl == do assignment plusths->re += r.re;ths->im += r.im;return *ths;}complex c3;c3 = __doapl(&c1, c2);inline complex __doapl(complex *ths, const complex &r){// doapl == do assignment plusths->re += r.re;ths->im += r.im;return *ths;}complex *c3 = new complex();*c3 = __doapl(&c1, c2);// 调用者需要思考一下是写 c3 还是 *c3调用该函数的人不用知道传递的东西是什么形式的(by reference或by value)
-
return by Reference Const
如果可以的话尽量返回reference,为了更快。
不能返回reference的情况:需要返回一个新创建的对象,而不能用已有的代替,因为在函数结束时,新创建的对象也没了!
friend友元
Section titled “friend友元”声明一个friend函数,可以不经过public方法,直接获得类对象的private数据。
class complex{ public: complex(double r = 0,double i = 0): re(r) ,im(i){}·
double real() const {return re;} // inline function double img() const;// declaration private: double re,im;
// 声明友元 friend complex& __doapl(complex*, const complex&);}
inline complex& __doapl(complex* ths,const complex& r){ ths->re += r.re; ths->im += r.im; return *ths;}相同class的各个Objects互为友元
class complex{ public: complex(double r = 0,double i = 0): re(r) ,im(i){}
int func(const complex& param){ return param.re; } private: double re,im;}
{ complex c1(2,1); commplex c2; c2.func(c1);}操作符重载和临时对象
Section titled “操作符重载和临时对象”c++里操作符也是一种函数,可以重新定义的
类内部的操作符重载
Section titled “类内部的操作符重载”inline complex& __doapl(complex *ths, const complex &r){// doapl == do assignment plus ths->re += r.re; ths->im += r.im; return *ths;}
// 1inline complex& += (const complex &r){ return __doapl(this, r);}
// 2 是错的 this不能写出来 他是被编译器处理之后的结果(下图是示意图)// 编译器处理后的this可能是第一个参数,也可能是最后一个参数,取决于不同的编译器inline complex& += (this, const complex &r){ return __doapl(this, r);}编译器看待“c2 += c1”
+= (c2, c1)
+=是一个函数

this
所有的成员函数都含有一个隐藏的参数: this, 可以把它写出来。
谁调用这个函数,谁就是那个this。
this是个指针,指向调用函数的对象
为什么要设计成有返回值 而不是void: 为了 c1 += c2 += c3
从后往前: c2 += c3 然后 c1 += c2
类外的操作符重载
Section titled “类外的操作符重载”c2 = c1 + c2c2 = 7 + c1//多种加法,所以不能在类里面重载inline complex operator + (const complex &x, const complex &y){ return complex(x.real()+y.real(), x.img()+y.img());}
inline complex operator + (int x, const complex &y){ return complex(x + y.real(), x.img()+y.img());}==绝不能返回引用, 因为函数体内创建的临时对象会在函数结束后清空==

cout << c1;// 这里的<<只能写成非成员操作符重载,因为cout这个对象改不了#include <iostream>
ostream&operator << (ostream &os, const complex &x){ return os << real(x) << " " << img(x);}
// 为了解决一连串的情况,所以不能设置成void返回值cout << c1 << c2;// 这个是从左往右的 cout << c1 再 << c2上述返回值可以用&(不是临时对象), 不能加const(为了连续输出)
complex(a, b)complex()int(7)这样的写法就是创建临时对象, 它的生命在下一行就结束了
需要在本行就进行其他操作,比如
return complex()std::cout << complex()auto a = complex()==设计一个class应该注意的事情==
Section titled “==设计一个class应该注意的事情==”- 善用构造函数的initilization list
- 成员函数加不加const, 应加尽加,否则会有副作用
- 参数、返回值传递尽量pass by reference、return by reference,考虑能不能用reference(临时变量)
- 数据尽可能放在private
BigThree:拷贝构造、拷贝函数、析构
Section titled “BigThree:拷贝构造、拷贝函数、析构”// 设计一个string.h#ifndef __MYSTRING__#define __MYSTRING__
class string{
}#endif
// 能够实现下列操作
string s1();string s1("hello");string s3(s1); // 拷贝构造
cout << s3 << endl;s3 = s2; // 拷贝复制cout << s3 << endl;如果没有写拷贝构造和拷贝复制,编译器会自动给写一份,它的实现是一个byte一个byte的去复制。
如果一个类里面有一个指针,就需要自己去实现拷贝,否则编译器默认的拷贝会把拷贝过去的指针指向同一个地方,而不是开一个新空间。
class String{ String(const char* cstr = 0); // ascii 0 is null String(const String &str); // 拷贝构造 String& operator = (const String &str); // 拷贝函数 ~String(); // 析构 char* get_c_str() const {return m_data};
private: char* m_data; // 可以动态增长,用指针}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'; }}
String::~String(){ delete[] m_data;}
{ string s1(); string s2("hello"); string *p = new string("hello"); delete p;
// 前两个生命周期结束时(离开作用域)会自动调用析构函数 // 最后一个得手动delete}浅拷贝(不拷贝值)
根据编译器默认的拷贝实现方式(一个bit一个bit拷贝),这种写法会造成内存泄漏
开了一块内存,但没有指针指着(再也不能管理到这块内存了)

深拷贝
string::string(const string &str){ m_data = new char[strlen(str.m_data) + 1]; // 相同class的各个Objects互为友元 strcpy(m_data, str.m_data);}
string s1("hello");string s2(s1);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;}==Array new一定要搭配Array Delete==
ostream& operator<<(ostream &os, const string &str){ os << str.get_c_str(); return os;}堆、栈、内存管理
Section titled “堆、栈、内存管理”栈:stack,存在于一个作用域的一块内存空间
堆:heap, 操作系统提供的全局的内存空间
{ Complex c1(1,2); // stack 离开作用域时生命自动消失(调用析构函数) static Complex c2(1,2); // 离开作用域时生命还会存在 Complex *p = new Complex(3); // heap 离开作用域时需要手动释放,否则会一直存在}{Complex *p = new Complex;...delete p; //一定要delete或者传出去}new 的时候内部是 malloc、转型、构造函数
delete的时候内部是析构函数和释放内存
对于String来说 析构函数清空data的内存、然后编译器再删除String类内的char *指针
new的时候会分配很多额外的空间,比如8字节的complex,实际能分配64(debug mode)/ 16(release mode)字节,包括三或四个部分
上下cookie(记录分配出去的大小)、类内容、debug额外所需部分、padding。
动态分配数组
String *p = new String[3];delete[] p; // 调用三次析构函数
String *p = new String[3];delete p; // 调用一次析构函数(在string这种动态分配的class的情况下会引起内存泄漏 )class Fraction{ public: ... // non-explicit-one-argument ctor Fraction(int num, int den = 1):m_numerator(num),m_denominator(den){}
operator double() const{ // 转换函数一般都会加const return (double) const(m_numerator / m_denominator); } private: int m_numerator; int m_denominator; }}
Fraction f(3,5);double d = 4 + f;// 编译器处理:// 1. 找有没有全局的operator +(int, Fraction)// 2. 找转换函数,f能不能转为doubleclass Fraction{ public: ... // non-explicit-one-argument ctor Fraction(int num, int den = 1):m_numerator(num),m_denominator(den){} } private: int m_numerator; int m_denominator; }}
Fraction d2 = f + 4;// 编译器处理:// non-explicit-one-argument ctor: 将4转化为fraction两种方法写一起可能造成编译器矛盾
class Fraction{ public: ... // non-explicit-one-argument ctor Fraction(int num, int den = 1):m_numerator(num),m_denominator(den){}
operator double() const{ // 转换函数一般都会加const return (double) const(m_numerator / m_denominator); } private: int m_numerator; int m_denominator; }}
Fraction f(3,5);Fraction d = 4 + f;// 把f转为double 还是 把4转为fraction解决==> explicit
表示构造函数只有显示的写的时候才能做,而不能隐式转化
explicit Fraction(int num, int den = 1):m_numerator(num),m_denominator(den){}类指针类(Pointer-like Class) shared_ptr和iterator
Section titled “类指针类(Pointer-like Class) shared_ptr和iterator”templete<class T>class shared_ptr{ public: T& operator*() const{return *px} // 定义 *A 取指针对应内容的操作
T* operator->() const{return px}; // -> 操作符 会作用在他前面的那个变量上,返回值是T*
shared_ptr(T *p):px(p){} private: T* px; long *pn;}
struct Foo{ void method(){...}}
sp = shared_ptr<Foo> (new Foo());Foo f(*sp);sp->method();// ==> 箭头作用在sp上,转化为 px->method();// ==> 箭头符号的特殊性, 会继续执行下去, 而不像其他符号执行一次就停止templete<class T>struct __list_node{ void* prev; void *next; T data;}
templete<class T,class Ref, class ptr>struct __list_iterator{ typedef __list_node<T>* link_type; link_type node;
ref operator*() const{ return (*node).data}; ptr operator->() const {return &(operator*())};}
list<Foo> iter;iter->method();// ===> &(*iter)->method类函数类(function-like class)
Section titled “类函数类(function-like class)”templete<class T1, class T2>struct pair{ T1 first; T2 second; pair():first(T1()),second(T2()){} pair(const &t1, const &t2):first(t1),second(t2){}}
temple<class Pair>struct select1st{ const typename Pair::first_type& operator()(const Pair &x) const{ {return x.first}; }}模版 template
Section titled “模版 template”只有这个位置 typename等价于class
temple <class T> // 或 temple <typename T>class complex{ public: complex(T r = 0, T i = 0):re(r), im(i){}}complex<int> c1(2, 1);templete<class T>inline const T& min(const T &a, const T &b ){ return b < a ? b : a;}模版在的文件会先编译一次,不会有什么问题;
之后还会和其他文件一起再编译一次,如果该类型T,没有重载 < 还是会报错。
templete <class T1, class T2>
struct pair{ T1 first; T2 second; pair(const &T1 a, const &T2 b):first(a),second(b){};
templete<class U1, class U2> pair(const pair<U1,U2> &p) first(p.first),second(p.second){}}
class Derived1: public Base1{
}
class Derived2: public Base2{
}
pair<Derived1, Derived2> p;pair<Base1, Base2> p2(p);特化 <—> 泛化
全特化
templete <class key>struct hash { };
// 特化,某些指定类型时特殊处理templete <>struct hash<char>{ size_t operator()(char x) const {return x};}
struct hash<int>{ size_t operator()(int x) const {return x};}
struct hash<long>{ size_t operator()(long x) const {return x};}
// 特化 specializationcout << hash<long>()(1000);偏特化
(1)个数上的偏
templete<typename T, typename Alloc=...>class vector{
};
templete<typename T, typename Alloc=...>class vector<bool, Alloc>{
}(2) 范围上的偏
templete <typename T>class C{
}
templete <typename U>class C<U*>{ ...}
//C<string> c1;C<string*> c2;templete<typename T, templete <typename T> class Container>class XCLs{ private Container<T> c;}
XCLs<string, list> mylst;C++标准库
Section titled “C++标准库”Containers(容器)
Section titled “Containers(容器)”- Sequence:array, vector, deque, forwar_list, list
- container adaptors: stack, queue, priority_queue
- associate : set, multiset, map, multimap
- unordered associate: unordered_set, unordered_multiset, unordered_map, unordered_multimap
