一、头文件的防御式声明(video2)

#ifndef __COMPLEX__
#define __COMPLEX__ //内容 #endif

二、初步感受模板(video2)

定义的时候:

//复数的实部和虚部可能是int,float等不同类型,使用模板来统一兼容他们
template <typename T> class Complex{
public:
Complex(T re, T im) {} private:
T re, im;
};

使用的时候:

Complex<int>(, );
Complex<float>(5.0, 4.0);

三、Inline方法(video3)

当方法在class定义的body中实现,就是Inline方法。

class Complex{
public:
Complex(double re, double im) {} //Inline方法
double real(){ return re}
double imag(){ return im} private:
double re, im;
};

理论上来说,将所有的方法都在Body中实现,全部变为Inline方法是最好的,因为Inline方法的执行速度最快。

但是对于编译器来说,是否将该方法变为真正的Inline方法,要看方法是否复杂,编译器是否有能力将其变为Inline方法。

四、访问级别public和private(video3)

class Complex{
//一般将一些提供给外部访问的方法写在public中
public:
Complex(double re, double im) {} //Inline方法
double real(){ return re}
double imag(){ return im}
//一般将一些处理内部事务的方法和数据变量等写在private中
private:
double re, im;
//public和private可以交错着写,不用死板的写为两段
private:
//Todo
public:
//Todo
};

五、构造函数(video3)

class Complex {
public:
//构造函数
Complex(double r = , double i = ) :re(r), im(i) {} double real() { return re; }
double imag() { return im; }
private:
double re, im;
};

构造函数中得re(r),im(i)相当于把实参赋值给变量re和im,这是构造函数特有的特性,其他普通函数是没有的。

建议使用这种方式来赋值,虽然也可以在函数体中使用赋值的方式来传入参数,但不建议。

实例化方式:

//生成Compelx对象,返回对象
Complex c = Complex(5.4, 4.5);
//同上
Complex c2(2.1, 3.9);
//生成Compelx对象,返回对象的指针
Complex *c3 = new Complex(5.6, 5.6);

六、函数的重载(video3)

class Complex {
public:
//两个构造函数实现不同方式创建对象
Complex(double r, double i) :re(r), im(i) {}
Complex() :re(), im() {}
//函数重载,两个real函数虽然名字相同,但参数和返回值都不同
double real() { return re; }
void real(double r) { re = r; } double imag() { return im; }
private:
double re, im;
};

函数对于编译器来说,名字回转换成人类无法阅读的形式,例如 ?real@Complex@@QBENXZ

所以,当函数参数、返回值等不同的时候,实际上对于编译器来说是两个不同的函数。

七、函数中使用const(video4)

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
private:
double re, im;
};

在real()方法中使用了const,表示该方法内部不会对数据进行修改。如果不使用const,则表示在方法内部可能对数据进行修改。

在使用的时候:

//对象c2被定义为const,即不可修改
const Complex c2(2.1, 3.9);
cout << c2.real() << endl; //调用成功
c2.real(5.0); //调用失败

c2被定义为const,即值不可修改。

那么使用c2.real()打印re的值时,由于定义real()方法时指定方法为const,所以编译器认为该方法不会对数据进行修改,是安全的,调用成功。

使用c2.real(5.0)调用另外一个赋值的real方法,在定义时未指定为const,所以编译器认为该方法可能修改数据,而c2又是const的,所以产生冲突,调用失败。

结论:当我们明确一个方法不会对数据进行修改时,尽量将方法指定为const的。

这里补充一个指针使用const:(2020-3-3)

const int *p = ;  # 表示值不能被修改
*p = ; # 错误
int a = ;
p = &a; # 正确 int * const p = ; # 表示p指针不能重新定向
*p = ; # 正确
int a = ;
p = &a; # 错误

如果const在"*"的左边,则"左定值"。如果const在"*"的右边,则"右定向"

八、参数传递(传值或引用)和值的返回(返回值或引用)(video4)

传值:pass by value

传引用:Pass by reference

传引用但不能修改:Pass by reference with const

尽量使用传引用的方式才传递参数,因为引用的底层是指针(C语言中指针作为参数传递类似),传递的数据大小为4个字节,速度会很快。

同样,值的返回也尽量返回引用(如果可以的话)。

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {}
//参数为一个Complex对象的引用,并且是const的,表示不能修改
void add(const Complex& c) {
this->re += c.real();
this->im += c.imag();
c.real(9.0); //报错,由于传进来的c的引用是const的,不能修改
} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
private:
double re, im;
};

使用:

Complex c1(2.1, 3.9);
Complex c2(4.5, 3.6);
//将c2的引用传入c1.add(),但是add不能c2造成影响
c1.add(c2);
cout << c1.real() << endl;

注意:当使用引用作为参数传递时,如果形参指明是const的,则在函数内部不能对其进行数据修改,如果进行修改,编译器报错。如果未指明const,则函数对其进行修改,会影响传入对象的本体。

九、友元friend(video4)

使用关键字friend将方法设置为友元方法,友元方法可以直接读取私有数据。

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
private:
double re, im;
//将friend_func函数定义为该类的友元,友元函数可以直接访问私有数据
friend void friend_func(const Complex& c);
}; //使用友元直接访问数据
inline void friend_func(const Complex& c){
cout << c.re << endl;
}

使用:

Complex c1(2.1, 3.9);
//使用友元直接访问数据
friend_func(c1);

另一种友元的场景:

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
//该方法传入另一个Complex,但是在函数内部可以直接访问私有数据
void func(const Complex& c) {
cout << c.re << endl;
cout << c.im << endl;
}
private:
double re, im;
};

使用:

Complex c1(2.1, 3.9);
//使用友元直接访问数据
Complex c2(4.5, 4.3);
//将c2传入c1.func()中
c1.func(c2);

为什么会有这种情况呢,按理说应该无法直接访问c2的私有数据的。

解释:用一句话解释,同一class所衍生出来的对象互为友元(friend)。

十、返回值什么时候返回引用(video4)

inline Complex& complex_add(Complex& c1, const Complex& c2) {
//c1的re值发生了改变,c2的re值未改变
c1.real(c2.real());
//c1是已经存在的空间,所以可以返回引用
return c1;
} inline Complex complex_add2(const Complex& c1, const Complex& c2) {
//重新生成一个Complex对象
Complex c_res = Complex(, );
c_res.real(c1.real() + c2.real());
c_res.imag(c1.imag());
//由于c_res是一个local变量,函数结束,它的生命周期就结束了,所以不能使用引用返回,而必须返回值
return c_res;
}

使用:

Complex c1(2.1, 3.9);
Complex c2(4.5, 4.3);
//使用引用接收
Complex& rp = complex_add(c1, c2);
//使用值接收
Complex c = complex_add2(c1, c2);
cout << rp.real() << endl;
cout << c.real() << endl;

结论:当返回的东西是已经存在的,即已经在外部分配了地址空间,则直接返回引用,效率会更高。如果返回的东西是新建的局部变量,那么必须以值的形式返回,否则函数结束后,该变量会被回收。

十一、操作符重载(成员函数)(video5)

当我们定义的类之间需要进行某些运算,但普通的操作符并不支持,那么我们可以自定义这些操作符。例如复数相加,我们可以自己定义“+=”的操作。

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
void imag(double i) { im = i; }
//重载+=操作符,实现复数相加
Complex& operator += (const Complex& c) {
return __doapl(this, c);
} private:
double re, im;
//将+=的实现细节抽取出来单独一个方法实现
inline Complex& __doapl(Complex* ths, const Complex& c) {
ths->re += c.re;
ths->im += c.im;
return *ths;
}
};

对于成员函数 Complex& operator += (const Complex& c);实际上有有两个参数(this, const Complex& c),this是一个指针,指向调用该函数的对象。

所以对于__doapl函数来说,就有两个参数,是将c2加到ths上去,并返回ths。

使用:

Complex c1(2.1, 3.9);
Complex c2(4.5, 4.3);
c1 += c2;
cout << c1.real() << endl;

__doapl中得ths就是c1。

引申思考:当我们仅执行 c1+=c2 时,我们的 Complex& operator += (const Complex& c);函数的返回值是可以用void代替的,已经完成了加法,无需返回Complex&。

但是当我们 c3+=c1+=c2 这样连续使用呢?当c1+=c2完成操作后,并未返回Complex&,则c3就无法进行+=操作了。

十二、操作符重载(非成员函数)(video5)

//两个复数相加,实部加实部,虚部加虚部
inline Complex operator + (const Complex& c1,const Complex& c2) {
return Complex(c1.real() + c2.real(), c1.imag() + c2.imag());
}
//复数加一个double数,将double数加到实部
inline Complex operator + (const Complex& c1, const double c2) {
return Complex(c1.real() + c2, c1.imag());
}
//一个double数加复数,将double数加到实部
inline Complex operator + (const double c1, const Complex& c2) {
return Complex(c2.real() + c1, c2.imag());
}

使用:

Complex c1(2.1, 3.9);
Complex c2 = c1 + 5.2;
Complex c3 = 6.2 + c1;
cout << c2.real() << endl; //输出7.3
cout << c3.real() << endl; //输出8.3

十三、将+、-号重载为复数的正负号(video5)

//+作为正号
inline Complex operator + (Complex& x) {
return x;
}
//-作为负号
inline Complex operator - (Complex& x) {
return Complex(-x.real(), -x.imag());
}

使用:

Complex c1(2.1, 3.9);
cout << (-c1).real() << endl;
cout << (+c1).real() << endl;

十四、重载<<操作符输出复数到屏幕(video5)

//重载<<,让cout可以直接输出复数
inline ostream& operator << (ostream& os,const Complex& x) {
return os << '(' << x.real() << ',' << x.imag() << ')';
}

当然,该重载函数也可以作为成员函数写到Complex类的定义中去。

使用:

Complex c1(2.1, 3.9);
cout << c1 << endl;

1-6 video总结:

1.习惯使用初始化列表,构造函数中,Complex(double r = 0, double i = 0) : re(r), im(i) {}

2.成员函数是否指明const,取决于该方法是否修改数据,若不修改数据,则尽可能指明const。因为可能会影响后续调用(特别是合作开发,其他人使用该接口)。当对象定义为const,则该对象无法调用非const成员方法。

3.参数传递和结果返回,在允许的情况下,尽量使用传递和返回引用。效率更高。

4.数据都放到private下,对外提供的函数接口放在public,内部自己用的方法放到private。public和private不是强制写成两段的,可以分成多段。

5.同一个类衍生出的对象之间都互为友元,可以直接调用对方的私有变量。

6.操作符重载都是作用在左边的变量上的(即调用该操作符函数的对象),注意连续调用的情况(返回不要为void),操作符重载可以是成员方法,也可以是全局方法。

7.成员方法在类定义中实现,就可能成为inline方法(视编译器的决定)。如果是全局函数,则需要在函数最前面加inline关键字,让编译器尽量的将其变为inline函数。

C++程序设计1(侯捷video 1-6)的更多相关文章

  1. C++程序设计2(侯捷video all)

    一.转换函数Conversion function(video2) 一个类型的对象,使用转换函数可以转换为另一种类型的对象. 例如一个分数,理应该可以转换为一个double数,我们用以下转换函数来实现 ...

  2. C++程序设计1(侯捷video 7-13)

     一.Big three(拷贝构造.拷贝赋值.析构函数)(video7) Big three指三个特殊函数,分别是拷贝构造函数.拷贝赋值和析构函数. 什么时候需要拷贝构造.拷贝赋值.析构函数: 当类中 ...

  3. C++标准库(体系结构与内核分析)(侯捷第二讲)

    一.OOP和GP的区别(video7) OOP:面向对象编程(Object-Oriented programming) GP:泛化编程(Generic programming) 对于OOP来说,我们要 ...

  4. 快笑死,侯捷研究MFC的原因

    与我研究VCL框架代码的原因一模一样:就是N年了,感觉自己还是没有掌握Delphi,惊叹别人各种各样神奇的效果,自己却不会,更不知为什么这样做,离高手的距离还有十万八千里.而且编程的时候,就像侯捷说的 ...

  5. 侯捷C++ Type traits(类型萃取

    泛型編程編出來的代碼,適用於任何「吻合某種條件限制」的資料型別.這已成為撰寫可復用代碼時的一個重要選擇.然而,總有一些時候,泛型不夠好 — 有時候是因為不同的型別差距過大,難以產生一致的泛化實作版本. ...

  6. 评侯捷的<深入浅出MFC>和李久进的<MFC深入浅出>

    侯捷的<深入浅出mfc>相信大家都已经很熟悉了,论坛上也有很多介绍,这里我就不多说了. 而李久进的<mfc深入浅出>,听说的人可能就少得多.原因听说是这本书当时没有怎么宣传,而 ...

  7. 侯捷STL学习(一)

    开始跟着<STL源码剖析>的作者侯捷真人视频,学习STL,了解STL背后的真实故事! 视频链接:侯捷STL 还有很大其他视频需要的留言 第一节:STL版本和重要资源 STL和标准库的区别 ...

  8. list源码4(参考STL源码--侯捷):transfer、splice、merge、reverse、sort

    list源码1(参考STL源码--侯捷):list节点.迭代器.数据结构 list源码2(参考STL源码--侯捷):constructor.push_back.insert list源码3(参考STL ...

  9. list源码1(参考STL源码--侯捷):list节点、迭代器、数据结构

    list源码1(参考STL源码--侯捷):list节点.迭代器.数据结构 list源码2(参考STL源码--侯捷):constructor.push_back.insert list源码3(参考STL ...

随机推荐

  1. USER_AGENT 知识

    USER-AGENT 是 Http 协议中的一部分,属于头域的组成部分,User Agent也简称 UA,意为用户代理,当用户通过浏览器发送 http 请求时,USER_AGENT 起到表明自己身份的 ...

  2. Cocos2d-x移植WP8时间CCScrollView问题

    cocos2d-x 2.2中的CCScrollView和CCTableView存在bug.导致区域裁剪错误 我是这样解决的. 在CCEGLView::setScissorInPoints里.依据不同旋 ...

  3. Hadoop入门实验

    一.实验目的 了解Hadoop的MapeReduce工作原理 二.实验内容 实现基于单机的伪分布式运行模拟 三.实验需要准备的软件和源 1.Jdk1.6以上 下载地址:http://www.oracl ...

  4. 食谱API自由和开放接口-为了发展自己的健康厨房APP应用

    什么时候健康厨房 (cook.yi18.net)上线后,基于接口的须要,就非常快就完毕的食谱API接口的开发 文档地址:http://doc.yi18.net/cookapidoc 菜谱食谱API , ...

  5. WPF获取控件内部的ScrollViewer,并控制ScrollViewer操作

    //获取内部  ScrollViewer方法 public static T FindVisualChild<T>(DependencyObject obj) where T : Depe ...

  6. 海洋cms 模板标签手册

    海洋cms采用极其简单易用的模板技术,所有标签直接调用接口,无需复杂的编码技术,让你对界面设计得心应手,请认真阅读本文档,妥善收藏. ========= 目录 =========00.相关必要说明01 ...

  7. WPF三维图形

    原文:WPF三维图形 wpf 三维图形基础生成三维图形的基本思想是能得到一个物体的三维立体模型(model).由于我们的屏幕只有二维,因而我们定义了一个用于给物体拍照的照相机(Camera).拍到的照 ...

  8. C#原子性运算 interlocked.compareExchanged

    缘起: 假设有一个类myClass, myclass里有一个count属性. 在多线程的环境下 每个线程中 直接使用count++,  如果两个线程并行执行时, 两个线程中的一个的结果会被覆掉, 非线 ...

  9. [WPF疑难] 模式窗口被隐藏后重新显示时变成了非模式窗口

    原文:[WPF疑难] 模式窗口被隐藏后重新显示时变成了非模式窗口 [WPF疑难] 模式窗口被隐藏后重新显示时变成了非模式窗口 周银辉 现象: 大家可以试试下面这个很有趣但会带来Defect的现象:当我 ...

  10. Windows 10开发基础——XML和JSON (二)

    主要内容: Linq to XML Newtonsoft.Json.Linq来解析JSON 博客园RSS(http://www.cnblogs.com/rss)的解析 UWP调用自己实现的Web AP ...