Home >Backend Development >C#.Net Tutorial >Thinking in C++ Volume 1 Reading Notes Key Summary

Thinking in C++ Volume 1 Reading Notes Key Summary

php是最好的语言
php是最好的语言Original
2018-07-28 11:43:281554browse

This article is mainly about reading some notes from the first volume of Thinking in C. Mainly some points to note

  • Thinking in C Chapter 2

    • ##Translator:

    • Compiler steps to compile the program:

    • Declaration and definition of functions or variables

    • Connection

  • Thinking in C Chapter 3

    • Function return description

    • Usual functions Library

    • Instructions on for loop

    • switch description

    • Specifier(specifier)

    • void*Pointer description

    • Valid scope of variables

    • Global variables

    • Static (static) variable

    • Connection (linkage type)

    • Preprocessing macro

    • typedef syntax description

    • enum description:

  • Thinking in C Chapter 5

    • Friends

    • Nested friends

    • struct other

  • Thinking in C Chapter 6

    • Constructor corresponding description

    • delete void*

    • Default constructor

    ##Thinking in C Chapter 7
    • overload
    • Type-safe linkage
    • Union
    • Default parameter usage rules:
    • Placeholder parameters
    Thinking in C Chapter 8
    • const Description
    • const pointer
    • Assignment and type checking
    • Temporary variable
    • Transfer and return address
    • Standard parameter transfer
    • Intra-class data members are const initialized
    • Constants during compilation
    • const objects and member functions
    Thinking in C Chapter 9
    • Inline functions
    • Constructor and destructor hidden behavior
    • Preprocessing Device
    Thinking in C Chapter 10
    • static
    • static Object
    • Internal link
    • others
    • stactic/const/stctic const
    • Static member function
    Pointer array/array pointer
  • Function pointer
    • Function Address
    • Function pointer
    • Call function pointer
    • Array of function pointers
    • typedef simplifies workload
    • ##namespace
  • namespace
    • Unnamed namespace
    • using directive& using declaration
    • new/malloc/delete/free
  • new/malloc
    • delete/free
    • Overload global new and delete
    • Class overloads new and delete
    • Overload new and delete for arrays delete
    • Position new/delete
    • Inheritance&Composition
  • Initialization expression
    • The order of constructor and destructor calls
    • Non-automatically inherited functions
    • Inheritance and static member functions
    • Private inheritance
    • Up type conversion and copy constructor
    • Operator overloading
  • =,-=,*=,/=,= and other types of operator overloading
    • Function parameters and return value description
    • Prefix & Postfix
    • Constant value return and return value optimization
    • operator[]
    • ##operator->(pointer indirect reference operator)

    • operator-> *

    • Basic guidelines for operator member functions

    • copy-on-write

    • Automatic typing Translate

    • ##Thinking in C Chapter 2
Translator:

Interpreter( interpreter)

  1. Compiler

  2. Compiler Compiler steps:

Preliminary The processor (preprocessor) processes the preprocessing instructions

  1. . The compilation is divided into two passes. The first pass parses the preprocessing code to generate the number of nodes, and performs global optimizer before proceeding to the second step. ), the second pass code generator parses the code tree to generate machine language or assembly language

  2. Static type checking is performed in the first pass

    Declaration and definition of functions or variables

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

    Connection

    The final stage of the compilation process, connect the target module generated by the compiler into an executable file (can be recognized by the operating system )

    Thinking in C Chapter 3

    Function return description

    The return statement exits the function and returns to the point after the function call, the operation of the Lenovo stack

    Usually function libraries

    The object module is managed by the library manager. This library manager manages the .lib/.a files

    Instructions on the for loop

    for(initialization;conditional;step)

    The for loop first performs initialization, and then judges conditional. If it is satisfied, it enters the loop. After executing the loop, proceed to the step step

    switch description

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

    The selector in the switch must be an integer value, and the integral-value must be The integer value

    selector can also be an enum type value

    The specifier (specifier)

    is used to change the meaning of the basic built-in type and convert the basic The type is expanded into a larger set
    1. int: short int / int / long int (when using short and long, the int keyword can be omitted)
    2. float/double: There is no long float, only long double , that is: float / double /long double
    3. signed/unsigned: sign bit (applicable to integer and character types)

    void* pointer description

    void* pointer can be assigned as Any type of address, but type information will be lost. Improper type conversion will cause the program to crash.

    The effective scope of the variable

    Starts from the definition point to the closest location before the variable is defined. The first closing bracket of the pair of open brackets, that is, the scope is determined by the last pair of brackets in which the variable is located.

    Global variables

    The life cycle of global variables is until the end of the program. You can use the extern keyword to use global variables in another file

    Static (static) variables

    The advantage of a static variable is that it is not available outside the scope of the function and cannot be easily changed, making errors localized. When static is applied to the function name and all external variables of the function, it means "in the file This name cannot be used externally", that is, it has a file scope such as

    //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,即全局静态变量声明是有文件作用域的,编译器将会产生错误

    Link (linkage type)

    1. Internal link: only the file being compiled is stored. space, create a separate storage space for each identifier, the internal connection is specified by the keyword static

    2. External connection: Create a separate storage space for all compiled files, that is, all Variables and functions are contained in this space

      Automatic (local) variables only exist temporarily on the stack, the linker does not know about automatic variables, so these variables are not connected

    Preprocessing macro

    #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 syntax description

    typedef existing-type-description alias-name

    1. typedef unsinged long ulong;

    2. typedef int* intPtr

    3. ##typedef struct MyStruct{

      //This is the structure definition of C Changying
      } MyStruct;

    4. typedef int (*fun)(int,int) //The function pointer alias is fun

    enum description:

    The check for enum in c is more strict. A (a is a color enumeration) is allowed in c, but it is not allowed in c because a has been converted twice. First, the color type is converted to int, and then it is incremented by 1. , convert the value into color, it is illegal during the second conversion

    Thinking in C Chapter 5

    Friends

    Can be accessed using the friend keyword Internal private member variables or member functions

    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*) refers to the address of an X object. The compiler knows how to pass an address, no matter what is passed. Objects, addresses have a fixed size. When trying to pass the entire object, the compiler must know the entire definition of X to determine its size and how to pass it, using an incomplete type specification, that is, declaring struct X before struct Y ;

    Nested friends

    The nested structure cannot automatically obtain access to private members. You can use the following method to access

    1. Declaration nested Set structure

    2. Declare that the structure is a friend used in the global scope

    3. Define the structure

    4. 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 Others

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

    The struct Cheshire in the Handler.h file is an incomplete type description or class declaration, and the specific class definition is placed in the implementation file

    Thinking in C Chapter 6

    Constructor corresponding instructions

    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;
    }

    The above code reports an error

    Switch will skip the sequence point of the constructor. Even when the constructor is not called, this object will also play a role in the subsequent program block, and an error will occur here
    is to ensure that the object is initialized at the same time as it is created. goto will also produce such errors.

    delete void*

    When void* points to a non-built-in type object, only the memory will be released and the destructor will not be executed

    默认构造函数

    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视频教程第一季

The above is the detailed content of Thinking in C++ Volume 1 Reading Notes Key Summary. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn