Thinking in C++ 第一卷阅读全书笔记重点总结

本文主要是阅读Thinking in C++ 第一卷的一些笔记。主要是一些注意点

  • Thinking in C++ Chapter 2

    • Translator:

    • 编译器编译程序步骤:

    • 函数或者变量的声明与定义

    • 连接

  • Thinking in C++ Chapter 3

    • 函数返回说明

    • 通常函数库

    • 关于for循环的说明

    • switch说明

    • 说明符(specifier)

    • void*指针说明

    • 变量的有效作用域

    • 全局变量

    • 静态(static)变量

    • 连接(linkage种类)

    • 预处理宏

    • typedef语法说明

    • enum说明:

  • Thinking in C++ Chapter 5

    • 友元

    • 嵌套友元

    • struct其他

  • Thinking in C++ Chapter 6

    • 构造函数相应说明

    • delete void*

    • 默认构造函数

  • Thinking in C++ Chapter 7

    • overload

    • 类型安全连接(type-safe linkage)

    • Union

    • 默认参数使用规则:

    • 占位符参数

  • Thinking in C++ Chapter 8

    • const说明

    • const指针

    • 赋值和类型检查

    • 临时变量

    • 传递和返回地址

    • 标准参数传递

    • 类内数据成员为const初始化

    • 编译期间的常量

    • const对象和成员函数

  • Thinking in C++ Chapter 9

    • 内联函数

    • 构造函数和析构函数隐藏行为

    • 预处理器

  • Thinking in C++ Chapter 10

    • static

    • static对象

    • 内部链接

    • others

    • stactic/const/stctic const

    • 静态成员函数

  • 指针数组/数组指针

  • 函数指针

    • 函数地址(Function Address)

    • 函数指针

    • 调用函数指针

    • 函数指针数组

    • typedef简化工作量

  • namespace

    • namespace

    • 未命名的名字空间

    • using directive& using declaration

  • new/malloc/delete/free

    • new/malloc

    • delete/free

    • 重载全局new和delete

    • 类重载new和delete

    • 为数组重载new和delete

    • 定位new/delete

  • 继承与组合(Inheritance&Composition)

    • 初始化表达式

    • 构造函数和析构函数调用的顺序

    • 非自动继承的函数

    • 继承与静态成员函数

    • 私有继承

    • 向上类型转换和拷贝构造函数

  • 运算符重载

    • +=,-=,*=,/=,=等一类运算符重载

    • 函数参数和返回值说明

    • Prefix ++ & Postfix ++

    • 常量传值返回与返回值优化

    • operator[]

    • operator->(指针间接引用运算符)

    • operator->*

    • 运算符成员函数基本方针

    • copy-on-write

    • 自动类型转换

Thinking in C++ Chapter 2

Translator:

  1. 解释器(interpreter)

  2. 编译器(complier)

编译器编译程序步骤:

  1. 预处理器(preprocessor)处理预处理指令

  2. 编译分为两遍,第一遍解析预处理代码生成节点数,在进行第二步之前进行全局优化(global optimizer),第二遍代码生成器(code generator)解析代码树生成机器语言或者汇编语言

静态类型检查在第一遍中进行

函数或者变量的声明与定义

void function();//声明void function(){}//定义extern int a;//声明int a;//定义

连接

编译过程的最后阶段,把编译器生成的目标模块连接成可执行文件(操作系统可以识别)

Thinking in C++ Chapter 3

函数返回说明

return语句退出函数,返回到函数调用后的那点,联想栈的操作

通常函数库

由库管理器来管理对象模块,这个库管理器就是管理.lib/.a文件的

关于for循环的说明

for(initialization;conditional;step)

for循环首先执行initialization,其次判断conditional,满足则进入循环,执行完循环进行step步骤

switch说明

switch(selector){
    case integral-value:statement;break;    ...
    defualt:statement;
}

switch中的selector必须为整数值,integral-value必须为整形数值

selector也可以为enum类型值

说明符(specifier)

用于改变基本内建类型的含义并将基本类型扩展成一个更大的集合
1. int: short int / int / long int(使用short和long时,int关键字可以省略)
2. float/double: 没有long float只有long double,即:float / double /long double
3. signed/unsigned:符号位(适用于整形和字符型)

void*指针说明

void*指针可以赋值为任何类型的地址,但是会丢失类型信息,不恰当的类型转换会导致程序崩溃

变量的有效作用域

从定义点开始,到和定义变量之前最邻近的开括号配对的第一个闭括号,也就是说作用域由变量所在的最近一对括号确定。

全局变量

全局变量的生命周期一直到程序结束,可以使用extern关键字来使用另一个文件中的全局变量

静态(static)变量

static变量优点是在函数范围之外它是不可用的,不可以轻易改变,使错误局部化,当应用static于函数名和所有函数外部变量时,它的意思是“在文件的外部不可以使用该名字”,即拥有文件作用域如

//file1.cppstatic int fs;int main(){
    fs = 1;
}//file2.cppextern int fs;//编译器不会找到file1.cpp文件中的fs,即文件作用域void function(){
    fs = 100;
}
extern static int i;int main(){    cout << i << endl;
}static int i = 0;
将出现error,即全局静态变量声明是有文件作用域的,编译器将会产生错误

连接(linkage种类)

  1. 内部连接:只对正在编译的文件穿件存储空间,为每一个标识符创建单独的存储空间,内部连接由关键字static指定

  2. 外部连接:对所有编译过的文件创建一个单独的存储空间,即将所有变量和函数包含在该空间中

    自动(局部)变量只是临时存在于堆栈中,连接器不知道自动变量,所以这些变量没有连接

预处理宏

#define PRINT(STR,VAR) \
    cout << STR << VAR << endl; \
    cout << VAR << STR <<endl

即可以像调用函数一样调用PRINT,这里预处理宏分行使用&#39;\&#39;,宏只是展开,并替换

#define PRINT(STR,VAR) \
    cout << #STR << VAR << endl

这里&#39;#&#39;表示STR字符串化(stringizing),比如:int i = 0;PRINT(i,i); //这里输出应该是 i:0

typedef语法说明

typedef existing-type-description alias-name

  1. typedef unsinged long ulong;

  2. typedef int* intPtr

  3. typedef struct MyStruct{
    //这里是C常营的结构定义
    } MyStruct;

  4. typedef int (*fun)(int,int) //函数指针别名为fun

enum说明:

c++中对enum的检查更为严格,c中允许a++(a为color型枚举),但是c++中不允许,因为a++做了两次转换,首先将color类型转换为int,然后自增1之后,将该值在转换成color,第二次转换时非法的

Thinking in C++ Chapter 5

友元

使用friend关键字可以访问内部私有成员变量或者成员函数

struct X;struct Y{    void f(X*);
};struct X{    private:    int i;    public:    void initialize();    friend void g(X*,int); //Global friend
    friend void Y::f(X*); //struct member friend
    friend struct z;    //Entire struct is a friend
    friend void h();
}

Y::f(X*)引用了一个X对象的地址,编译器知道如何传递一个地址,不管被传递的是什么对象,地址具有固定大小,当试图传递整个对象时,编译器必须知道X的全部定义以确定它的大小以及如何传递,使用不完全类型说明(incomplete type specification),即在struct Y之前声明struct X;

嵌套友元

嵌套结构不能自动获得访问private成员权限,可以使用如下方法访问

  1. 声明嵌套结构

  2. 声明该结构是全局范围内使用的一个friend

  3. 定义该结构

const in sz = 20;struct Holder{    private:    int a[sz];    public:    void initialize();    struct Pointer;
    friend Pointer;    struct Pointer{    private:
    Holder* h;    int* p;    public:    void initialize(Holder* h);    void next();    void previous();    void top();    void end();    int read();    void set(int i);
    };
};void Holder::initialize(){
    memset(a,0,sz*sizeof(int));
}void Holder::Pointer::initialize(Holder* rv){
    h = rv;
    p = rv->a;
}
...int main(){
    Hodler h;
    Holder::Pointer hp;    int i;
    h.initialize();
    hp.initialize(&h);
    ...
}

struct其他

//: Hadler.h
class Handler{
    struct Cheshire;
    Cheshire* smile;
    public:    ...};
//:~
//:Handler.cpp
struct Handler::Cheshire{
    int i;    ...}...

Handler.h文件中struct Cheshire是一个不完全的类型说明或者类声明,具体类定义放在了实现文件中

Thinking in C++ Chapter 6

构造函数相应说明

class Object{public:    Object(int number = 0);private:    int m_number;
};//:~//: mainint main(int argc, char *argv[])
{    int i = 0;    switch(i){    case 0:        Object obj1{1};        break;    case 1:            //error: cannot jump from switch statement to this case label "case 1:"
        Object obj2{2};   //jump bypasses variable initialization "Object obj1{1};"
        break;
    }    return 0;
}

上述代码报错
switch回跳过构造函数的的序列点,甚至构造函数没有被调用时,这个对象也会在后面的 程序块中程序块中起作用,这里产生错误
是确保对象在产生的同时被初始化。goto也会产生这样的错误。

delete void*

当void*指向一个非内建类型的对象时,只会释放内存,不会执行析构函数

默认构造函数

class Object{public:    Object(int number);private:    int m_number;
};Object object[2] = {Object{1}};

Object没有默认构造函数,数组声明初始化时将报错,object[1]必须有默认构造函数进行初始化,否则报错当且仅当没有构造函数时编译器会自动创建一个默认构造函数

Thinking in C++ Chapter 7

overload

使用范围和参数可以进行重载

void f();class X{void f();};

类型安全连接(type-safe linkage)

1.cppvoid functin(int);2.cppvoid function(char);int main(){    function(1); //cause a linker error;
    return 0;
}

编译成功,在C中连接成功,但是在C++中连接出错,这是C++中的一种机制:类型安全连接

Union

class SuperVar{    enum{
    character,
    integer,
    floating_point
    } vartype;    union{    char c;    int i;    float f;
    };    public:
    SuperVal(char ch);
    SuperVal(int ii);
    SuperVal(float ff);    void print();
};

SuperVal::SuperVali(char ch){
    vartype = character;
    c = ch;
}
SuperVal::SuperVali(int ii){
    vartype = integer;
    i = ii;
}
SuperVal::SuperVali(float ff){
    vartype = floating_type;
    f = ff;
}void SuperVal::print(){    switch(vartype){    case character:    cout << "character:" << c <<endl;    break;    case integer:    cout << "integer :" << i <<endl;    break;    case floating_point:    cout << "float :" << f <<endl;    break;
    }
}int main(){
    SuperVar A(&#39;c&#39;),B(12),C(1.44f);
    A.print();
    B.print();
    C.print();    return 0;
}
  1. enum没有类型名,因为后面没有必要涉及美剧的类型名称,所以枚举类型名可选,非必须

  2. union没有类型名和标识符,称为匿名联合(anonymous union),不需要使用标识符和以点操作符方式访问这个union的元素

  3. 访问一个匿名联合成员就像访问普通变量一样,唯一区别在于:该联合的两个变量占用同一内存空间,如果匿名union在文件作用域内(在所有函数和类之外),则它必须声明为static,以使它有内部的连接

默认参数使用规则:

  1. 只有参数列表的后部参数才可以是默认的

  2. 一旦在一个函数调用中开始使用默认参数,那么这个参数后面的所有参数都必须为默认的

占位符参数

void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2

其中version 2除了i,flt之外中间参数就是占位符参数

Thinking in C++ Chapter 8

const说明

C++中const默认为内部连接,仅在const被定义的文件中才可见,在连接时不能被其它编译单元看见,当定义一个const时必须赋值给它,除非使用extern进行说明

extern const int bufsize;

通常c++不为const创建空间,将其定义保存在符号表内,但是上面的extern进行了强制内存空间分配,另外如取const的地址也是需要存储空间的分配。

对于复杂的结构,编译器建立存储,阻止常量折叠。在C中const默认为外部连接,C++默认为内部连接.出现在所有函数外部的const作用域是整个文件,默认为内部连接

const指针

  1. const修饰指针正指向的对象 const int* a;

  2. const修饰在指针中的地址 int* const a;

赋值和类型检查

  1. const对象地址不可以赋值给一个非const指针,但是可以吧一个非const对象地址赋值给一个const指针

  2. 字符数据的字面值:

char * cp = "howdy";char cp[] = "howdy";

指针cp指向一个常量值,即常量字符数组,数组cp的写法允许对howdy进行修改

临时变量

在求表达式值期间,编译器必须创建零时变量,编译器为所有的临时变量自动生成为const

传递和返回地址

void t(int*) {}void u(const int* clip){  //*clip = 2; error
  int i = *clip;  //int * ip2 = clip error;}const char* v(){  return "result of functin 0";
}const int * const w(){  static int i;  return &i;
}int main(){  int x= 0;  int * ip = &x;  const int * cip = &x;
  t(ip); //ok
  //t(cip);  not ok;
  u(ip); //ok
  u(cip);//ok
  //char * cp = v(); not ok
  const char* ccp = v();//ok
  //int * ip2 = w(); not ok
  const int * const ccip = w();// ok
  const int* cip2 = w();//ok
  //*w() = 1; not ok}
  1. const指针不可以赋值给非const指针,但是非const指针可以赋值给const指针

  2. 函数v()返回一个从字符数组的字面值中建立的const char *,在编译器建立了它并把它存储在静态存储区之后,该声明实际上产生该字符数组的字面值的地址

  3. 函数w()返回值要求这个指针以及这个指针所指向的对象均为常量,与函数v()类似,因为i是静态的,所以函数返回后返回值仍然有效

  4. const int* const w()只有在作左值时第二个const才能显现作用,所以w()返回值可以赋值给const int *

标准参数传递

可以将临时对象传递给const引用,但不能将一个临时对象传递给接收指针的函数,对于指针必须明确接受地址(临时变量总是const)

class X
{public:    X() {}
};
X f(){return X();}void g1(X&){ }void g2(const X&){ }int main(int argc, char *argv[])
{
    g1(f()); //error!
    g2(f());    return 0;
}

类内数据成员为const初始化

必须在构造函数的初始化列表中进行初始化

编译期间的常量

  1. 一个内建类型的static const可以看成编译期间的常量,但是该static const必须在定义的地方进行初始化

  2. 无标记enum也可以看成为编译期间常量,一个枚举在编译期间必须有值

class X{
  enum {size = 1000};  //same as static const
  static const int size = 1000;  int i[size];
}

const对象和成员函数

  1. 若将一个成员函数声明为const,则该成员函数可以被const对象调用

  2. const成员函数可以调用非const成员和const成员,非const成员函数同样可以使用const成员

  3. const对象只能调用const成员函数,非const对象调用非const成员函数

Thinking in C++ Chapter 9

内联函数

内联函数与普通函数一样执行,但是内联函数在适当的地方像宏一样展开,不需要函数调用的开销(压栈,出栈),任何在类内部定义的函数自动成为内联函数

  1. 内联函数体过大时,编译器将放弃使用内联

  2. 当取函数地址时,编译器也将放弃内联

  3. 一个内联函数在类中向前引用一个还没有声明的函数时,是可以的,因为C++规定只有在类声明结束后,其中的内联函数才会被计算

class Forward{    int i;    public:    Forward():i(0){}    int f() const {return g()+i;}    int g() const {return i;}
}

构造函数和析构函数隐藏行为

class X
{    int i,j,k;public:
    X(int x = 0):i(x),j(x),k(x) { cout << "X" <<endl;}   //X(int x):i(x),j(x),k(x){cout << "X" <<endl;}
    ~X(){cout << "~X" <<endl;}
};class Y{
    X q,r,s;    int i;public:
    Y(int ii):i(ii){cout << "Y" <<endl;}
    ~Y(){        cout << "~Y" <<endl;
    }
};int main(int argc, char *argv[])
{
    Y y(1);    return 0;
}//:~//:outputX
X
X
Y
~Y
~X
~X
~X
  1. 类中包含子对象,构造函数先调用子对象的构造函数,如果没有默认构造函数,则必须在初始化列表中进行初始化,然后在调用类的构造函数

  2. 类中包含子对象,先调用类的析构函数在调用子类的析构函数

预处理器

1. #define DEBUG(x) cout << #x "=" << x<<endl;#:字符串化,详见REF2. #define FIELD(a) char* a##_string;int a##_size##:标志粘贴,允许设两个标识符并将它们粘贴在一起产生新的标识符

Thinking in C++ Chapter 10

static

  1. 在固定地址上进行存储分配,在一个特殊的静态数据区上创建,不是在堆栈上产生

  2. 对一个特定编译单位来说是局部的,static可以控制名字的可见性,该名字在这个单元或者类以外是不可见的

static对象

  1. 如果没有为一个内建类型的静态变量提供一个初始化值,编译器会确保在程序开始时它被初始化为零(转化成适当的类型)

  2. 如果在定义一个静态对象时没有指定构造参数时,该类必须有默认的构造函数

内部链接

常量、内联函数默认情况下为内部链接

others

  1. 所有全局对象隐含为静态存储

  2. 对static函数意味着只在本单元可见,成为文件静态(file static)

stactic/const/stctic const

  1. static成员必须在类外初始化,如果不初始化,则编译器不会进行默认初始化,对于非内建类型,可以使用构造函数初始化代替“=”操作符

  2. 类内const成员必须在构造函数的初始化列表中进行初始化

  3. static const变量(内建类型)必须在声明的地方就初始化

  4. static对象数组,包括const和非const数组必须在类外部初始化

  5. 自定义class类声明为stctic,不管其为const或者非const都必须在类外初始化

  6. 类的静态成员必须进行初始化后才可以使用

静态成员函数

  1. 类的静态成员函数不能访问一般数据成员或者函数,只能访问静态数据成员,也能调用其他静态成员函数

  2. 静态成员函数没有this指针

指针数组/数组指针

优先级低的先读
*p[] 指针数组
(*p)[] 数组指针,即指向一个数组

函数指针

函数地址(Function Address)

函数的地址:函数名后不跟参数

void fun(){}fun即为函数地址fun()为函数的调用

函数指针

double pam(int); //prototypedouble (*pf)(int); //function pointerpf=pam;//pf now points to the pam();

调用函数指针

double x=pf(5);double x=(*pf)(5);

函数指针数组

const double* f1(const double ar[],int n);const double* f2(const double [],int);const double* f3(coanst double *,int);//f1,f2,f3函数声明本质一样const double* (*pa[3])(const double * , int) = {f1,f2,f3};  //[]优先级高于*,所以表示pa是个数组,数组中包含三个指针auto pb = pa;const double * px = pa[0](av,3);const double * py = (*pb[1])(av,3);double x = *pa[0](av,3);double y = *(pb[1])(av,3);

指向整个数组的指针,即是一个指针,而不是一个数组,优先级低的先读
*p[] 指针数组
(*p)[] 数组指针,即指向一个数组

const double*  (*(*pd)[3])(const double* , int) = &pa;->等价形式 auto pd = &pa;

pd指向数组,*pd就是数组,而(*pd)[i]是数组中的元素,即函数指针
函数调用:

(*pd)[i](av,3)->此处返回const double *
*(*pd)[i](av,3)->此处返回 double
另外一种函数调用略复杂(*(*pd)[i])(av,3)->返回const double *
*(*(*pd)[i])(av,3)->返回 double

typedef简化工作量

typedef const double* (*p_fun)(const double* ,int);
p_fun p1 = f1;
p_fun pa[3] = {f1,f2,f3};
p_fun (*pd)[3] = &pa;

namespace

namespace

  1. namespace只能在全局范围内定义,但是可以相互嵌套

  2. 在namespace定义的结尾,右花括号后面不必跟一个分号

  3. 可以按类的语法来定义一个namespace,定义的内容可以在多个头文件中延续,就好像重复定义这个namespace

//:header1.h
namespace MyLib{
    extern int x;
    void f();
    //...}
//:~
//:header2.h
namespace MyLib{
    extern int y;
    void g();
    //...}
  1. 一个namespace的名字可以用另一个名字来作为它的别名
    namespace lib = MyLib;

  2. 不能像类一样创建一个名字空间的实例

未命名的名字空间

namespace {
    class A{};
    class B{};
    int i,j,k;
    //...}

将局部名字放在一个未命名的名字空间中,不需要加上static就可以作为内部连接

using directive& using declaration

using directive:using namespace xxusing declaration:using xx::f;

new/malloc/delete/free

new/malloc

new计算内存大小,并调用构造函数,malloc需要手工计算内存大小,不调用构造函数

delete/free

delete先执行析构函数,在清空内存,free直接清空内存
delete用于void*时将不会调用析构函数,直接清空内存

重载全局new和delete

  1. 重载的new必须有一个size_t参数,该参数由编译器产生并传递给我们,分配内存的长度,必须返回一个指向等于该长度的对象的指针,如果没有找到存储单元,则返回一个0,然而如果找不到存储单元,不能仅仅返回0,还应该有new-handler或产生一个异常信息

  2. new返回void*,而不是指向任何特定类型的指针,只需要完成内存分配,而不是完成对象的创建,直到构造函数调用才能完成对象的创建,调用构造函数是编译器完成的

  3. delete参数是由new分配的void*指针,该参数是在调用析构函数后得到的指针,析构函数从存储单元中移去对象

#include <cstdio>#include <cstdlib>using namespace std;void* operator new(size_t sz){    printf("operator new:%d Bytes\n",sz);    void* m = malloc(sz);    if(!m) puts("out of memry");    return m;
}void operator delete(void* m){    puts("operator delete");    free(m);
}class S{    int i[100];public:
    S(){puts("S::S()");}
    ~S(){puts("S::~S()");}
};int main(int argc, char *argv[])
{    int * p = new int(47);    delete p;
    S* s = new S;    delete s;
    S* sa = new S[3];    delete [] sa;    return 0;
}//:outputoperator new:4 Bytesoperator deleteoperator new:400 Bytes
S::S()
S::~S()operator deleteoperator new:1208 Bytes
S::S()
S::S()
S::S()
S::~S()
S::~S()
S::~S()operator delete

这里使用printf(),puts()等函数,而不是iostreams,因为使用iostreams对象时(全局对象cin,cout,cerr),调用new分配内存,printf不会进入死锁状态,它不调用new来初始化自身

类重载new和delete

//p328

主要思想:使用static数组以及一个bool数组,返回static数组的下标地址,进行new,得到static下标数组进行delete
void* operator new(size_t) throw(bad_alloc);
void operator delete(void*);

为数组重载new和delete

//p331

定位new/delete

//p333

主要思想:使用new运算符重载,但是不对delete运算符重载,指定内存位置new
void* operator new(size_t,void*);

继承与组合(Inheritance&Composition)

初始化表达式

  1. 成员对象如果没有默认的构造函数,必须显示的进行初始化,即在构造函数初始化列表中进行初始化

  2. 只有执行成员类型初始化之后,才会进入构造函数

  3. 没有对所有成员以及基类对象的构造函数调用之前,若基类没有默认构造函数则必须初始化,即在初始化列表中进行初始化,否则无法进入构造函数体

构造函数和析构函数调用的顺序

  1. 构造函数调用的顺序:首先调用基类构造函数,然后调用成员对象的构造函数,调用成员对象构造函数的顺序是按照成员对象在类中声明的顺序执行,最后调用自己的构造函数

  2. 析构函数调用次序与构造函数调用次序相反,先调用自己的析构函数,在调用成员函数的析构函数,最后调用基类析构函数

  3. 对于多重继承,构造函数调用顺序为继承时的声明顺序

非自动继承的函数

  1. 构造函数

  2. 析构函数

  3. operator=

继承与静态成员函数

静态成员函数与非静态成员函数的共同特点:
1. 均可以被继承到派生类中
2. 重新定义一个静态成员,所有基类中的其他重载函数会被隐藏
3. 如果我们改变了基类中的函数的特征,所有使用该函数名字的基类版本将会被隐藏。
4. 静态成员函数不可以是虚函数

私有继承

  1. 使用私有继承是为了不允许该对象的处理像一个基类对象,一般private更适合于组合

  2. 私有继承成员公有化

class Base{    public:    Base(){}
    ~Base(){}    void name() {cout << "Base name" <<endl;}
}
class Derived:private Base{    public:    Derived(){}
    ~Derived(){}    using Base::name; //私有继承公有化}

向上类型转换和拷贝构造函数

class Base{    public:    Base(){}
    Base(const Base&){}
    ~Base(){}    void name() {cout << "Base name" <<endl;}
}
class Derived:public Base{    public:    Derived(){}
    Derived(const Derived& d):Base(d){ }//这里是调用基类的拷贝构造函数
    ~Derived(){}
}

基类拷贝构造函数的调用将一个Derived引用向上类型转换成一个Base引用,并且使用它来执行拷贝构造函数,向上类型转换是安全的

运算符重载

+=,-=,*=,/=,=等一类运算符重载

首先进行自我检查(是否对自身赋值),即this == &val,在进行运算,“=”运算符只允许作为成员函数进行重载

函数参数和返回值说明

  1. 对于任何函数参数,如果仅需要从参数中读而不改变它,默认地应当做const引用传递。普通算数运算符(像“+”,“-”)和bool运算符不会改变参数,所以const引用为主要传递方式,当函数时成员函数时,就转换为const成员函数。只有会改变左侧参数的运算符赋值(如+=)和operator=,左侧参数不是常量,但因参数将被改变,所以参数仍然按地址传递

  2. 返回值类型取决于运算符的具体含义,如果使用该运算符产生一个新值,就需要产生一个作为返回对象的新对象。例如operator+必须生成一个操作数之和的对象,该对象作为一个常量通过返回值返回,所以作为一个左值不会被改变

  3. 所有赋值运算符均改变左值,为了使赋值结果能用于链式表达式(如a=b=c),应该能够返回一个刚刚改变了的左值的引用。但该引用并非一定要是常量引用,如(a=b).f(),这里b赋值给a,a调用成员函数f,因此所有赋值运算符的返回值对于左值应该是非常量引用(如果是常量引用,则f成员函数必须为const函数,否则无法调用该函数,这与愿望相违背)

  4. 对于逻辑运算符,人们至少希望得到一个int返回值,或者最好是bool值

Prefix ++ & Postfix ++

  1. 成员函数

const Object& operator++(){}const Object operator++(int){}
  1. 友元函数

const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}

对于友元函数重载来说,因为传入的Object对象被改变,所以使用非常量引用
前缀通过引用返回,后缀通过值(临时对象)返回,因为后缀返回临时对象,所以后缀通过常量值返回,前缀返回引用,如果希望可以继续改变对象则返回引用,否则通过常量引用返回比较合适,这样与后缀保持了一致性

常量传值返回与返回值优化

  1. 作为常量通常通过传值方式返回。考虑二元运算符+,假设在表达式f(a+b)中使用,a+b的结果变为一个临时对象(Object),该对象被f()调用,因为它为临时的,所以自动被定义为常量,所以无论是否返回值为常量都没有关系。但是如果使用(a+b).f(),这里设返回值为常量规定了对于返回值只有常量成员函数才可以调用

  2. 返回值优化通过传值方式返回要创建的的新对象时,注意使用的形式,如operator+

version 1:return Object(lObj.i+rObj.i);
version 2:
Object tmp(lObj.i+rObj.i);return tmp;

version 2将会发生三件事,首先创建tmp对象,然后调用拷贝构造函数把tmp拷贝到外部返回值的存储单元中,最后当tmp在作用域的结尾时调用析构函数
version 1编译器直接将该对象创建在外部返回值的内存单元,不是整的创建一个局部变量所以仅需要一个普通的构造函数调用(不需要拷贝构造函数),且不会调用析构函数,效率高。这种方式被称为返回值优化。

operator[]

该运算符必须是成员函数,而且只接受一个参数,可以返回一个引用,可以用于等号左侧。

operator->(指针间接引用运算符)

该运算符一定是一个成员函数,它必须返回一个对象(或者引用),该对象也有一个指针间接引用运算符;或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指的内容

class Obj{    static int i,j;    public:    void f() const {cout<<i++<<endl;}    void g() const {cout<<j++<<endl;}
};int Obj::i = 47;int Obj::j = 11;class ObjContainer{    vector<obj* >a;    public:    void add(Obj* obj) {a.push_back(obj);}    friend class SmartPointer;
};class SmartPointer{
    ObjContainer& oc;    int index;    public:
    SmartPointer(ObjContainer & obj):oc(obj){
    index = 0;
    }    bool operator++(){    if(index >= oc.a.size()) return false;    if(oc.a[++index] == 0) return false;    return true;
    }    bool operator++(int){    return operator++();
    }
    Obj* operator->() const{    return oc.a[index];
    }
};

指针间接引用运算符自动的为用SmartPointer::operator->返回的Obj*调用成员函数

class Obj{    static int i,j;    public:    void f() const {cout<<i++<<endl;}    void g() const {cout<<j++<<endl;}
};int Obj::i = 47;int Obj::j = 11;class ObjContainer{    vector<obj* >a;    public:    void add(Obj* obj) {a.push_back(obj);}    class SmartPointer; //声明友元之前必须告知该类存在
    friend SmartPointer;    class SmartPointer{
        ObjContainer& oc;        int index;    public:
    SmartPointer(ObjContainer & obj):oc(obj){
        index = 0;
    }    bool operator++(){        if(index >= oc.a.size()) return false;        if(oc.a[++index] == 0) return false;        return true;
    }    bool operator++(int){        return operator++();
    }
    Obj* operator->() const{        return oc.a[index];
    }
    };
    SmartPointer begin(){    return SmartPointer(*this);
    }
};

operator->*

//p294

运算符成员函数基本方针

  1. 所有一元运算符建议为成员

  2. =()[]->->*必须为成员

  3. += -= /= *= ^= &= |= %= >>= <<=建议为成员

  4. 所有其他二元运算符为非成员

copy-on-write

//p301引用计数

自动类型转换

  • 构造函数转换:构造函数能把另外一个类型对象(或者引用)作为它的单个参数,该构造函数允许编译器执行自动类型转换

  • 运算符转换:运算符重载,创建一个成员函数,该函数通过关键字operator后跟随想要转换的类型的方法,将当前类型转换为希望的类型,自动类型转换只发生在函数调用值中,而不在成员选择期间

class Three{    int i;    public:    Three(int ii = 0,int = 0):i(ii){}
};
class Four{    int x;    public:    Four(int xx):x(xx){}    operator Three() const {return Three(x);}
};void g(Three){}int main(){
    Four four(1);
    g(four);
    g(1);
}
  • 二义性错误

class Orange;
class Apple{    public:    operator Orange() const;
};
class Orange{    public:    Orange(Apple);
};void f(Orange){}int main(){
    Apple a;    // f(a);  error:二义性错误}
  • 扇出错误:提供不止一种类型的自动转换

class Orange{};class Pear{};class Apple{
    public:
    operator Orange() const;
    operator Pear() const;
};void eat(Orange);void eat(Apple);int main(){
    Apple a;    //eat(a); error:扇出错误}

相关文章:

【读书笔记】精通CSS 第二版

《深入理解bootstrap》读书笔记:第一章 入门准备

相关视频:

李炎恢PHP视频教程第一季

以上就是Thinking in C++ 第一卷阅读全书笔记重点总结的详细内容,更多请关注其它相关文章!