Home > Article > Backend Development > Thinking in C++ Volume 1 Reading Notes Key Summary
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
##operator->(pointer indirect reference operator)
operator-> *
Basic guidelines for operator member functions
copy-on-write
Automatic typing Translate
Static type checking is performed in the first pass
void function();//声明void function(){}//定义extern int a;//声明int a;//定义
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 )
The return statement exits the function and returns to the point after the function call, the operation of the Lenovo stack
The object module is managed by the library manager. This library manager manages the .lib/.a files
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(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
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 can be assigned as Any type of address, but type information will be lost. Improper type conversion will cause the program to crash.
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.
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
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,即全局静态变量声明是有文件作用域的,编译器将会产生错误
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
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
#define PRINT(STR,VAR) \ cout << STR << VAR << endl; \ cout << VAR << STR <<endl 即可以像调用函数一样调用PRINT,这里预处理宏分行使用'\',宏只是展开,并替换 #define PRINT(STR,VAR) \ cout << #STR << VAR << endl 这里'#'表示STR字符串化(stringizing),比如:int i = 0;PRINT(i,i); //这里输出应该是 i:0
typedef existing-type-description alias-name
typedef unsinged long ulong;
typedef int* intPtr
//This is the structure definition of C Changying
} MyStruct;
Can be accessed using the friend keyword Internal private member variables or member functionsstruct 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 friendsThe nested structure cannot automatically obtain access to private members. You can use the following method to access
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); ... }
//: 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 fileThinking in C Chapter 6Constructor 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 errordelete void*When void* points to a non-built-in type object, only the memory will be released and the destructor will not be executedSwitch 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.
class Object{public: Object(int number);private: int m_number; };Object object[2] = {Object{1}};
Object没有默认构造函数,数组声明初始化时将报错,object[1]必须有默认构造函数进行初始化,否则报错当且仅当没有构造函数时编译器会自动创建一个默认构造函数
使用范围和参数可以进行重载
void f();class X{void f();};
1.cppvoid functin(int);2.cppvoid function(char);int main(){ function(1); //cause a linker error; return 0; }
编译成功,在C中连接成功,但是在C++中连接出错,这是C++中的一种机制:类型安全连接
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('c'),B(12),C(1.44f); A.print(); B.print(); C.print(); return 0; }
enum没有类型名,因为后面没有必要涉及美剧的类型名称,所以枚举类型名可选,非必须
union没有类型名和标识符,称为匿名联合(anonymous union),不需要使用标识符和以点操作符方式访问这个union的元素
访问一个匿名联合成员就像访问普通变量一样,唯一区别在于:该联合的两个变量占用同一内存空间,如果匿名union在文件作用域内(在所有函数和类之外),则它必须声明为static,以使它有内部的连接
只有参数列表的后部参数才可以是默认的
一旦在一个函数调用中开始使用默认参数,那么这个参数后面的所有参数都必须为默认的
void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2
其中version 2除了i,flt之外中间参数就是占位符参数
C++中const默认为内部连接,仅在const被定义的文件中才可见,在连接时不能被其它编译单元看见,当定义一个const时必须赋值给它,除非使用extern进行说明
extern const int bufsize;
通常c++不为const创建空间,将其定义保存在符号表内,但是上面的extern进行了强制内存空间分配,另外如取const的地址也是需要存储空间的分配。
对于复杂的结构,编译器建立存储,阻止常量折叠。在C中const默认为外部连接,C++默认为内部连接.出现在所有函数外部的const作用域是整个文件,默认为内部连接
const修饰指针正指向的对象 const int* a;
const修饰在指针中的地址 int* const a;
const对象地址不可以赋值给一个非const指针,但是可以吧一个非const对象地址赋值给一个const指针
字符数据的字面值:
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}
const指针不可以赋值给非const指针,但是非const指针可以赋值给const指针
函数v()返回一个从字符数组的字面值中建立的const char *,在编译器建立了它并把它存储在静态存储区之后,该声明实际上产生该字符数组的字面值的地址
函数w()返回值要求这个指针以及这个指针所指向的对象均为常量,与函数v()类似,因为i是静态的,所以函数返回后返回值仍然有效
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; }
必须在构造函数的初始化列表中进行初始化
一个内建类型的static const可以看成编译期间的常量,但是该static const必须在定义的地方进行初始化
无标记enum也可以看成为编译期间常量,一个枚举在编译期间必须有值
class X{ enum {size = 1000}; //same as static const static const int size = 1000; int i[size]; }
若将一个成员函数声明为const,则该成员函数可以被const对象调用
const成员函数可以调用非const成员和const成员,非const成员函数同样可以使用const成员
const对象只能调用const成员函数,非const对象调用非const成员函数
内联函数与普通函数一样执行,但是内联函数在适当的地方像宏一样展开,不需要函数调用的开销(压栈,出栈),任何在类内部定义的函数自动成为内联函数
内联函数体过大时,编译器将放弃使用内联
当取函数地址时,编译器也将放弃内联
一个内联函数在类中向前引用一个还没有声明的函数时,是可以的,因为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. #define DEBUG(x) cout << #x "=" << x<<endl;#:字符串化,详见REF2. #define FIELD(a) char* a##_string;int a##_size##:标志粘贴,允许设两个标识符并将它们粘贴在一起产生新的标识符
在固定地址上进行存储分配,在一个特殊的静态数据区上创建,不是在堆栈上产生
对一个特定编译单位来说是局部的,static可以控制名字的可见性,该名字在这个单元或者类以外是不可见的
如果没有为一个内建类型的静态变量提供一个初始化值,编译器会确保在程序开始时它被初始化为零(转化成适当的类型)
如果在定义一个静态对象时没有指定构造参数时,该类必须有默认的构造函数
常量、内联函数默认情况下为内部链接
所有全局对象隐含为静态存储
对static函数意味着只在本单元可见,成为文件静态(file static)
static成员必须在类外初始化,如果不初始化,则编译器不会进行默认初始化,对于非内建类型,可以使用构造函数初始化代替“=”操作符
类内const成员必须在构造函数的初始化列表中进行初始化
static const变量(内建类型)必须在声明的地方就初始化
static对象数组,包括const和非const数组必须在类外部初始化
自定义class类声明为stctic,不管其为const或者非const都必须在类外初始化
类的静态成员必须进行初始化后才可以使用
类的静态成员函数不能访问一般数据成员或者函数,只能访问静态数据成员,也能调用其他静态成员函数
静态成员函数没有this指针
优先级低的先读
*p[] 指针数组
(*p)[] 数组指针,即指向一个数组
函数的地址:函数名后不跟参数
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 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定义的结尾,右花括号后面不必跟一个分号
可以按类的语法来定义一个namespace,定义的内容可以在多个头文件中延续,就好像重复定义这个namespace
//:header1.h namespace MyLib{ extern int x; void f(); //...} //:~ //:header2.h namespace MyLib{ extern int y; void g(); //...}
一个namespace的名字可以用另一个名字来作为它的别名
namespace lib = MyLib;
不能像类一样创建一个名字空间的实例
namespace { class A{}; class B{}; int i,j,k; //...}
将局部名字放在一个未命名的名字空间中,不需要加上static就可以作为内部连接
using directive:using namespace xxusing declaration:using xx::f;
new计算内存大小,并调用构造函数,malloc需要手工计算内存大小,不调用构造函数
delete先执行析构函数,在清空内存,free直接清空内存
delete用于void*时将不会调用析构函数,直接清空内存
重载的new必须有一个size_t参数,该参数由编译器产生并传递给我们,分配内存的长度,必须返回一个指向等于该长度的对象的指针,如果没有找到存储单元,则返回一个0,然而如果找不到存储单元,不能仅仅返回0,还应该有new-handler或产生一个异常信息
new返回void*,而不是指向任何特定类型的指针,只需要完成内存分配,而不是完成对象的创建,直到构造函数调用才能完成对象的创建,调用构造函数是编译器完成的
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来初始化自身
//p328
主要思想:使用static数组以及一个bool数组,返回static数组的下标地址,进行new,得到static下标数组进行delete
void* operator new(size_t) throw(bad_alloc);
void operator delete(void*);
//p331
//p333
主要思想:使用new运算符重载,但是不对delete运算符重载,指定内存位置new
void* operator new(size_t,void*);
成员对象如果没有默认的构造函数,必须显示的进行初始化,即在构造函数初始化列表中进行初始化
只有执行成员类型初始化之后,才会进入构造函数
没有对所有成员以及基类对象的构造函数调用之前,若基类没有默认构造函数则必须初始化,即在初始化列表中进行初始化,否则无法进入构造函数体
构造函数调用的顺序:首先调用基类构造函数,然后调用成员对象的构造函数,调用成员对象构造函数的顺序是按照成员对象在类中声明的顺序执行,最后调用自己的构造函数
析构函数调用次序与构造函数调用次序相反,先调用自己的析构函数,在调用成员函数的析构函数,最后调用基类析构函数
对于多重继承,构造函数调用顺序为继承时的声明顺序
构造函数
析构函数
operator=
静态成员函数与非静态成员函数的共同特点:
1. 均可以被继承到派生类中
2. 重新定义一个静态成员,所有基类中的其他重载函数会被隐藏
3. 如果我们改变了基类中的函数的特征,所有使用该函数名字的基类版本将会被隐藏。
4. 静态成员函数不可以是虚函数
使用私有继承是为了不允许该对象的处理像一个基类对象,一般private更适合于组合
私有继承成员公有化
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,在进行运算,“=”运算符只允许作为成员函数进行重载
对于任何函数参数,如果仅需要从参数中读而不改变它,默认地应当做const引用传递。普通算数运算符(像“+”,“-”)和bool运算符不会改变参数,所以const引用为主要传递方式,当函数时成员函数时,就转换为const成员函数。只有会改变左侧参数的运算符赋值(如+=)和operator=,左侧参数不是常量,但因参数将被改变,所以参数仍然按地址传递
返回值类型取决于运算符的具体含义,如果使用该运算符产生一个新值,就需要产生一个作为返回对象的新对象。例如operator+必须生成一个操作数之和的对象,该对象作为一个常量通过返回值返回,所以作为一个左值不会被改变
所有赋值运算符均改变左值,为了使赋值结果能用于链式表达式(如a=b=c),应该能够返回一个刚刚改变了的左值的引用。但该引用并非一定要是常量引用,如(a=b).f(),这里b赋值给a,a调用成员函数f,因此所有赋值运算符的返回值对于左值应该是非常量引用(如果是常量引用,则f成员函数必须为const函数,否则无法调用该函数,这与愿望相违背)
对于逻辑运算符,人们至少希望得到一个int返回值,或者最好是bool值
成员函数
const Object& operator++(){}const Object operator++(int){}
友元函数
const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}
对于友元函数重载来说,因为传入的Object对象被改变,所以使用非常量引用
前缀通过引用返回,后缀通过值(临时对象)返回,因为后缀返回临时对象,所以后缀通过常量值返回,前缀返回引用,如果希望可以继续改变对象则返回引用,否则通过常量引用返回比较合适,这样与后缀保持了一致性
作为常量通常通过传值方式返回。考虑二元运算符+,假设在表达式f(a+b)中使用,a+b的结果变为一个临时对象(Object),该对象被f()调用,因为它为临时的,所以自动被定义为常量,所以无论是否返回值为常量都没有关系。但是如果使用(a+b).f(),这里设返回值为常量规定了对于返回值只有常量成员函数才可以调用
返回值优化通过传值方式返回要创建的的新对象时,注意使用的形式,如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编译器直接将该对象创建在外部返回值的内存单元,不是整的创建一个局部变量所以仅需要一个普通的构造函数调用(不需要拷贝构造函数),且不会调用析构函数,效率高。这种方式被称为返回值优化。
该运算符必须是成员函数,而且只接受一个参数,可以返回一个引用,可以用于等号左侧。
该运算符一定是一个成员函数,它必须返回一个对象(或者引用),该对象也有一个指针间接引用运算符;或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指的内容
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); } };
//p294
所有一元运算符建议为成员
=()[]->->*必须为成员
+= -= /= *= ^= &= |= %= >>= <<=建议为成员
所有其他二元运算符为非成员
//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:扇出错误}
相关文章:
相关视频:
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!