本篇内容
本篇记录的是【侯捷 – C++面向对象高级开发】入门课程。
本课程一共是上下两部分,上部分主要讲基础的OOP思想以及方法,下部分是深入的解析。总课时估计是十来小时,放在四五天学习会挺舒服的。
侯捷视频资源
源自公众号:编程指北
链接: https://pan.baidu.com/s/19REVrk-_3lpQu_fUmRBRUw 密码: 7iup
或有条件也可以直接访问Youtube资源。
篇2 头文件与类的声明
为什么面向对象
将数据和函数方法绑定在一起,方便(方法整合到了一起)、安全且隐私(数据不被观察)。
Header防卫式声明
多个文件引入头部文件的时候,避免重复定义。
攥写方法如下:
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
#endif
Header布局
前置声明、类声明、类定义。
class ostream;
class complex;
class complex{};
complex::function(){
}
类模板
希望类中某个属性不直接绑定内置类型,因此直接把类型抽象成一个模板。
template<typename T>
class complex{
public:
complex(T a,T b):re(a),im(b){}
private:
T re,im;
};
complex<double> c2(2.5,1.5)
complex<int> c1(2,6)
篇3 构造函数
inline 内联函数
如果函数在class内定义完成,则会成为inline函数(但要求函数简单,编译器不拒绝)。
但某些复杂函数无法inline,编译器会拒绝。
access level 访问级别
public / private
建议所有的数据都放在private。
构造函数
构造函数的通常写法
构造函数的函数名称与类一致。无返回值。
另外,不带指针的类通常不需要写析构函数。
complex(double r = 0,double i = 0):
re(r), im(i){ }
complex(double r = 0,double i = 0)
{ re = r ; im = i; }
支持重载,但是不可以让编译器产生歧义,即入两个函数都可以(等价可以)
重载不仅指入参可以不一样,出参也可以不一样。
用作成员set和用作get的函数名可以一致。
double real() const{return re;}
void real(double r){re = r;}
complex(double r = 0,double i = 0):
re(r), im(i){ }
complex():
re(0), im(0){ }
Singleton 单例模式
把构造函数放在private,就不能被公共创造。
class A{
public:
static A& getInstance();
setup(){...}
private:
A();
A(const A& rhs);
}
A& A::getInstance(){
static A a;
return a;
}
篇4 参数传递与返回值
const member functions 常量成员函数
定义函数的时候,不希望该函数有修改成员值的权力,就加const限定词。
double real() const{ reutrn re; }
参数传递:pass by value vs. pass by reference(to const)
尽量传ref。
传value是复制了一份数据到栈上。
传ref是直接传引用(引用的地步就是指针,四个字节)。
所以传ref快,而且方便改值(尽管传char可能更小,一个字节,但是没必要这么细,通常来说传ref快)。当然如果希望不被改值,加const关键词。
complex& operator += (const complex&);
返回值传递:return by value vs. return by reference(to const)
尽量返ref。
friend 友元
通常封装数据之后,外部都无法访问数据。**但是友元可以。**事实上友元有点违反封装的设计原理。
class complex{
private:
friend complex& __doapl(complex*,const complex& r);
}
inline complex&
__doapl(complex* ths, const complex& r){
ths->re += r.re;
this->im += r.im;
return *ths;
}
相同class的各个objects互为friends
相同class的各个objects互为friends。即同类的objects可以互相访问private data。
篇5 操作符重载 operator overloading
成员函数版本 含this
因此需要在类中写好定义。
使用场景
complex c1;
complex c2;
c1 += c2;
complex c3;
c3 += c2 += c1;
则实现方式:
inline complex& complex::operator += (const complex& r){
return __doapl(this,r);
}
这里隐藏了操作符的左操作数,是this,参数列表中不需要显式写出,可以直接使用。
非成员函数版本 不含this
二元加法
使用场景
complex c1;
complex c2;
c2 = c1 + c2;
c2 = c1 + 5;
c2 = 7 + c1;
则实现方式:
这个绝对不可以返回reference,因为相加的object一定是新建且是local的。因此需要创建临时对象(无名称,只在当前行有用)返回。临时对象的内存地址与return出去后的内存地址一致吗?
一元正负
使用场景
complex c1;
+c1;
-c1;
实现方法
inline complex&
operator +(const complex& x){
return x;
}
inline complex&
operator -(const complex& x){
return complex(-real(x),-imag(x));
}
二元判断是否相等,是否不等
使用场景
complex c1;
complex c2;
c1 == c2;
c1 == 0;
0 == c1;
比较相等

比较不等

<<重载(std重载)
使用场景
complex c1(2,1);
cout << c1;
cout << c1 << c1;
实现方案
#include <iostream>
ostream&
operator << (ostream& os, const compelex& x){
return os << '(' << real(x) <<',' << imag(x) << ')';
}
以上是经典案例1,complex,class without pointer member
以上是经典案例2,string,class with pointer member
篇7 Big Three:拷贝构造、拷贝赋值、析构
场景
String s1();
String s2("我是一个酒精过敏的帅哥");
String s3(s1);
s3 = s2;
Big Three
类声明
**如果成员函数含有指针,必须含有copy ctor和copy op=。*否则如果使用默认的方法,只是潜赋值,会共用同一个底层的char。
class String {
public:
String (const char* cstr = 0);
String (const String& str);
String& operator=(const String& s);
~String();
char* get_c_str() const(return m_data);
private:
char* m_data;
}
constructor和destructor定义
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;
}
copy ctor(拷贝构造)
inline
String::String(const String& str){
m_data = new char[str(str.m_data) + 1];
strcpy(m_data,str.m_data);
}
copy assignment operator(拷贝赋值函数)
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;
}
篇8 栈stack、堆heap与内存管理
堆栈说明
- 栈,存在于某个作用域。
- 堆,全局内存空间,可动态分配内存。
{
Complex c1(1,2);
Complex* p = new Complex(3);
}
static local objects的生命周期
其生命在作用域结束后仍然存在
{
static Complex c;
}
global objects的生命周期
其生命一直存在,直到程序结束
Complex c3;
int main{
}
heap objects的生命周期
存在,直到被delete掉。如果不delete则内存泄漏(即没有回收,且没有办法再手动回收)。
{
Complex* p = new Complex;
delete p;
}
new的过程
- 先分配内存:使用malloc函数,大小为sizeof(对象大小),对象大小是成员变量决定的,(与成员函数无关)。
- 如果创建单个对象:如Complex两个double,则8字节,头尾要加上cookie,表明该部分内存已经被占用,一个cookie为4个字节。即release下8+2*4 = 16字节(必须是16字节的倍数,如果不足则向上取整)。debug下就得再加32+4,其实没必要记。
- 如果创建对象数组:除了要n份成员变量以外,需要额外一个字节来记录数组的大小。**即如创建长度为3的Complex数组,release下:8 * 3(成员变量) +4 * 2 (cookie)+ 4 (记录长度) = 36 -> 48 **
- 再调用构造函数