本文主要是阅读Thinking in C++ 第一卷的一些笔记。主要是一些注意点
静态类型检查在第一遍中进行
void function();//声明
void function(){}//定义
extern int a;//声明
int a;//定义
编译过程的最后阶段,把编译器生成的目标模块连接成可执行文件(操作系统可以识别)
return语句退出函数,返回到函数调用后的那点,联想栈的操作
由库管理器来管理对象模块,这个库管理器就是管理.lib/.a文件的
for(initialization;conditional;step)
for循环首先执行initialization,其次判断conditional,满足则进入循环,执行完循环进行step步骤
switch(selector){
case integral-value:statement;break;
...
defualt:statement;
}
switch中的selector必须为整数值,integral-value必须为整形数值
selector也可以为enum类型值
用于改变基本内建类型的含义并将基本类型扩展成一个更大的集合
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*指针可以赋值为任何类型的地址,但是会丢失类型信息,不恰当的类型转换会导致程序崩溃
从定义点开始,到和定义变量之前最邻近的开括号配对的第一个闭括号,也就是说作用域由变量所在的最近一对括号确定。
全局变量的生命周期一直到程序结束,可以使用extern关键字来使用另一个文件中的全局变量
static变量优点是在函数范围之外它是不可用的,不可以轻易改变,使错误局部化,当应用static于函数名和所有函数外部变量时,它的意思是“在文件的外部不可以使用该名字”,即拥有文件作用域如
//file1.cpp
static int fs;
int main(){
fs = 1;
}
//file2.cpp
extern int fs;//编译器不会找到file1.cpp文件中的fs,即文件作用域
void function(){
fs = 100;
}
extern static int i;
int main(){
cout << i << endl;
}
static int i = 0;
将出现error,即全局静态变量声明是有文件作用域的,编译器将会产生错误
自动(局部)变量只是临时存在于堆栈中,连接器不知道自动变量,所以这些变量没有连接
#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
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);
...
}
//: 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;
};
//:~
//: main
int 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也会产生这样的错误。
当void*指向一个非内建类型的对象时,只会释放内存,不会执行析构函数
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.cpp
void functin(int);
2.cpp
void 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 <break;
case integer:
cout << "integer :" << i <break;
case floating_point:
cout << "float :" << f <break;
}
}
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 1
void 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作用域是整个文件,默认为内部连接
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;
}
必须在构造函数的初始化列表中进行初始化
class X{
enum {size = 1000}; //same as static const
static const int size = 1000;
int i[size];
}
内联函数与普通函数一样执行,但是内联函数在适当的地方像宏一样展开,不需要函数调用的开销(压栈,出栈),任何在类内部定义的函数自动成为内联函数
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(int x):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" <cout << "~Y" <int main(int argc, char *argv[])
{
Y y(1);
return 0;
}
//:~
//:output
X
X
X
Y
~Y
~X
~X
~X
1. #define DEBUG(x) cout << #x "=" << x<#:字符串化,详见REF
2. #define FIELD(a) char* a##_string;int a##_size
##:标志粘贴,允许设两个标识符并将它们粘贴在一起产生新的标识符
常量、内联函数默认情况下为内部链接
优先级低的先读
*p[] 指针数组
(*p)[] 数组指针,即指向一个数组
函数的地址:函数名后不跟参数
void fun(){}
fun即为函数地址
fun()为函数的调用
double pam(int); //prototype
double (*pf)(int); //function pointer
pf=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;
//: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 namespace xx
using declaration:using xx::f;
new计算内存大小,并调用构造函数,malloc需要手工计算内存大小,不调用构造函数
delete先执行析构函数,在清空内存,free直接清空内存
delete用于void*时将不会调用析构函数,直接清空内存
#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;
}
//:output
operator new:4 Bytes
operator delete
operator new:400 Bytes
S::S()
S::~S()
operator delete
operator 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*);
静态成员函数与非静态成员函数的共同特点:
1. 均可以被继承到派生类中
2. 重新定义一个静态成员,所有基类中的其他重载函数会被隐藏
3. 如果我们改变了基类中的函数的特征,所有使用该函数名字的基类版本将会被隐藏。
4. 静态成员函数不可以是虚函数
class Base{
public:
Base(){}
~Base(){}
void name() {cout << "Base name" <private Base{
public:
Derived(){}
~Derived(){}
using Base::name; //私有继承公有化
}
class Base{
public:
Base(){}
Base(const Base&){}
~Base(){}
void name() {cout << "Base name" <public Base{
public:
Derived(){}
Derived(const Derived& d):Base(d){ }//这里是调用基类的拷贝构造函数
~Derived(){}
}
基类拷贝构造函数的调用将一个Derived引用向上类型转换成一个Base引用,并且使用它来执行拷贝构造函数,向上类型转换是安全的
首先进行自我检查(是否对自身赋值),即this == &val,在进行运算,“=”运算符只允许作为成员函数进行重载
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编译器直接将该对象创建在外部返回值的内存单元,不是整的创建一个局部变量所以仅需要一个普通的构造函数调用(不需要拷贝构造函数),且不会调用析构函数,效率高。这种方式被称为返回值优化。
该运算符必须是成员函数,而且只接受一个参数,可以返回一个引用,可以用于等号左侧。
该运算符一定是一个成员函数,它必须返回一个对象(或者引用),该对象也有一个指针间接引用运算符;或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指的内容
class Obj{
static int i,j;
public:
void f() const {cout<void g() const {cout<int Obj::i = 47;
int Obj::j = 11;
class ObjContainer{
vector 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<void g() const {cout<int Obj::i = 47;
int Obj::j = 11;
class ObjContainer{
vector 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引用计数
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:扇出错误
}