本文主要是阅读Thinking in C++ 第一卷的一些笔记。主要是一些注意点
Translator:
编译器编译程序步骤:
静态类型检查在第一遍中进行
函数或者变量的声明与定义
void function();//声明void function(){}//定义extern int a;//声明int a;//定义
连接
编译过程的最后阶段,把编译器生成的目标模块连接成可执行文件(操作系统可以识别)
函数返回说明
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种类)
自动(局部)变量只是临时存在于堆栈中,连接器不知道自动变量,所以这些变量没有连接
预处理宏
#define PRINT(STR,VAR) \\
cout << STR << VAR << endl; \\
cout << VAR << STR <即可以像调用函数一样调用PRINT,这里预处理宏分行使用'\\',宏只是展开,并替换
#define PRINT(STR,VAR) \\
cout << #STR << VAR << endl
这里'#'表示STR字符串化(stringizing),比如:int i = 0;PRINT(i,i); //这里输出应该是 i:0
typedef语法说明
typedef existing-type-description alias-name
enum说明:
c++中对enum的检查更为严格,c中允许a++(a为color型枚举),但是c++中不允许,因为a++做了两次转换,首先将color类型转换为int,然后自增1之后,将该值在转换成color,第二次转换时非法的
友元
使用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成员权限,可以使用如下方法访问
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是一个不完全的类型说明或者类声明,具体类定义放在了实现文件中
构造函数相应说明
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]必须有默认构造函数进行初始化,否则报错当且仅当没有构造函数时编译器会自动创建一个默认构造函数
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 <}
}int main(){
SuperVar A('c'),B(12),C(1.44f);
A.print();
B.print();
C.print(); return 0;
}
默认参数使用规则:
占位符参数
void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2
其中version 2除了i,flt之外中间参数就是占位符参数
const说明
C++中const默认为内部连接,仅在const被定义的文件中才可见,在连接时不能被其它编译单元看见,当定义一个const时必须赋值给它,除非使用extern进行说明
extern const int bufsize;
通常c++不为const创建空间,将其定义保存在符号表内,但是上面的extern进行了强制内存空间分配,另外如取const的地址也是需要存储空间的分配。
对于复杂的结构,编译器建立存储,阻止常量折叠。在C中const默认为外部连接,C++默认为内部连接.出现在所有函数外部的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)
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初始化
必须在构造函数的初始化列表中进行初始化
编译期间的常量
class X{
enum {size = 1000}; //same as static const
static const int size = 1000; int i[size];
}
const对象和成员函数
内联函数
内联函数与普通函数一样执行,但是内联函数在适当的地方像宏一样展开,不需要函数调用的开销(压栈,出栈),任何在类内部定义的函数自动成为内联函数
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" <~X(){cout << "~X" < };class Y{
X q,r,s; int i;public:
Y(int ii):i(ii){cout << "Y" <~Y(){ cout << "~Y" < }
};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<
static
static对象
内部链接
常量、内联函数默认情况下为内部链接
others
stactic/const/stctic const
静态成员函数
优先级低的先读
*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
//:header1.h
namespace MyLib{
extern int x;
void f();
//...}
//:~
//:header2.h
namespace MyLib{
extern int y;
void g();
//...}
未命名的名字空间
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
new计算内存大小,并调用构造函数,malloc需要手工计算内存大小,不调用构造函数
delete/free
delete先执行析构函数,在清空内存,free直接清空内存
delete用于void*时将不会调用析构函数,直接清空内存
重载全局new和delete
#include#include 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*);
初始化表达式
构造函数和析构函数调用的顺序
非自动继承的函数
继承与静态成员函数
静态成员函数与非静态成员函数的共同特点:
1. 均可以被继承到派生类中
2. 重新定义一个静态成员,所有基类中的其他重载函数会被隐藏
3. 如果我们改变了基类中的函数的特征,所有使用该函数名字的基类版本将会被隐藏。
4. 静态成员函数不可以是虚函数
私有继承
class Base{ public: Base(){}
~Base(){} void name() {cout << "Base name" <}
class Derived:private Base{ public: Derived(){}
~Derived(){} using Base::name; //私有继承公有化}
向上类型转换和拷贝构造函数
class Base{ public: Base(){}
Base(const Base&){}
~Base(){} void name() {cout << "Base name" <}
class Derived:public Base{ public: Derived(){}
Derived(const Derived& d):Base(d){ }//这里是调用基类的拷贝构造函数
~Derived(){}
}
基类拷贝构造函数的调用将一个Derived引用向上类型转换成一个Base引用,并且使用它来执行拷贝构造函数,向上类型转换是安全的
+=,-=,*=,/=,=等一类运算符重载
首先进行自我检查(是否对自身赋值),即this == &val,在进行运算,“=”运算符只允许作为成员函数进行重载
函数参数和返回值说明
Prefix ++ & Postfix ++
const Object& operator++(){}const Object operator++(int){}
const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}
对于友元函数重载来说,因为传入的Object对象被改变,所以使用非常量引用
前缀通过引用返回,后缀通过值(临时对象)返回,因为后缀返回临时对象,所以后缀通过常量值返回,前缀返回引用,如果希望可以继续改变对象则返回引用,否则通过常量引用返回比较合适,这样与后缀保持了一致性
常量传值返回与返回值优化
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<};int Obj::i = 47;int Obj::j = 11;class ObjContainer{ vectora; 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<};int Obj::i = 47;int Obj::j = 11;class ObjContainer{ vectora; 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
运算符成员函数基本方针
copy-on-write
//p301引用计数
自动类型转换
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++ 第一卷阅读全书笔记重点总结的详细内容,更多请关注其它相关文章!
更多技巧请《转发 + 关注》哦!