这篇文章主要讲述C++语言的基础。文章不涉及C++最基础的语法,主要为一些熟悉C++后需要注意和学习的点。包括的内容有const,mutable,virtual等。
const修饰
const成员函数不能调用非const成员函数,不能改变非static成员变量,可以改变static成员变量。
const成员函数可以改变mutable关键字修饰的成员变量。
const实例不能调用非const成员函数。
const type* pobj
表示pobj是指向的内容不可变;type* const pobj = &instance
表示pobj不能改,而且需要初始化。
返回值设置为const可以避免像(a * b) = c
这样的错误。当a和b为内置类型时,那直接了当的不合法。但当a和b为自定义类型时,是有可能是合法的,当为了避免这种没有意义的语句,将operator*的返回值设为const。
const_iterator迭代器指向的值不可更改;const std::vector<int>::iterator iter = vec.begin()
指iter不可更改。
类内const int x = 100;
在某些c++版本中不合法,建议用enum代替实现。
1 2 3 4 5 6 7 8 9 10 11 12 class Base {public : const int x; Base ():x (1 ) {} int & print (int ) { std::cout<<"in const function" <<std::endl; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> class Base {public : Base () {} const int & print (int ) const { std::cout<<"in const function" <<std::endl; } int & print (int ) { std::cout<<"in non-const function" <<std::endl; return const_cast <int &> ((static_cast <const Base>(*this )).print (1 )); } }; int main (void ) { Base obj1; const Base obj2; obj1.print (1 ); obj2.print (2 ); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> class Base {public : Base () {} const int & print (int ) const { std::cout<<"in const function" <<std::endl; } int & print (int ) { std::cout<<"in non-const function" <<std::endl; } }; int main (void ) { Base obj1; const Base obj2; obj1.print (1 ); obj2.print (2 ); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> class Base {public : Base () {} int & print (int ) { std::cout<<"in non-const function" <<std::endl; } }; int main (void ) { Base obj1; const Base obj2; obj1.print (1 ); obj2.print (2 ); return 0 ; }
mutable修饰 mutable关键字意思是可变的,是与const相对的关键字。 在类中,const成员函数不能修改非静态的成员变量。但是,使用mutable可以突破限制,const函数可以修改mutable修饰的非静态成员变量。
mutable只能用于修饰非静态成员变量。
explicit修饰 explicit关键字只能用于修饰类的构造函数,包括拷贝构造函数。表明该构造函数是显式的,即是不允许隐式转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <string> void log (const std::string& msg) { std::cout<<msg <<std::endl; } class Base {public : explicit Base (int ) { log ("in base construct func..." ); } explicit Base (const Base& in) { log ("in base copy contruct func..." ); } }; class Derive : public Base {public : Derive ():Base (1 ) {} }; int main (void ) { Base b = 1 ; Derive d; Base b2 = d; return 0 ; }
extern修饰 extern修饰一个变量时,表示这个变量为全局变量。
1 2 3 4 5 6 7 8 int global = 1132 ; int &globalr = global; static int local1 = 12 ; const int local2 = 13 ; extern const int hello = 14 ;
1 2 3 4 5 6 7 8 9 10 11 12 int main () { extern int global; std::cout<<global; extern const int local2; std::cout<<local2; extern const int hello; std::cout<<hello; return 0 ; }
因此,不能在头文件中定义全局变量。
volatile修饰 volatile意思为「不稳定的」。修饰变量时,表示变量可能会被未知的因素修改,比如硬件等。对于使用volatile修饰的变量,每次读取数据,系统都从变量所在内存重新读取。
virtual修饰 虚函数 虚函数实现多态性。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Base {public : virtual void print () { log ("in Base..." ); } }; class Derive : public Base {public : virtual void print () { log ("in Derive..." ); } }; int main (void ) { Derive *d = new Derive (); Base *b = d; b->print (); b->Base::print (); return 0 ; }
注意 :
只有在通过基类指针或引用间接指向派生类子类型时多态性才会起作用!
子类中的虚函数的声明必须与基类中的定义方式完全匹配。但有一个例外:返回对基类的引用(或指针)的虚函数,子类中的虚函数可以返回基类函数所返回类型的子类的的引用(或指针)。
通过基类指针或引用调用虚函数时,实参的默认值为在基类虚函数声明中指定的值。如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
对于父类和子类相同名字函数,
virtual修饰 基类函数:virtual Base::print 子类函数:virtual Derive:print
两个函数print具有相同的返回值类型(对于引用和指针有例外)和相同的形参列表:basePtr->print调用子类print,体现多态性
两个函数print具有不同形参列表:子类中的Base::print被隐藏,basePtr->print调用基类print
两个函数print具有不同的返回值,相同的形参列表:编译错误,overriding error…
无virtual修饰 基类函数:Base::print 子类函数:Derive:print
被隐藏的基类函数,可以通过Base::print的方式调用。 pderive->Base::print();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Base {public : Base () {} void print () { log ("in Base..." ); } void anotherPrint () { log ("in Base...anotherPrint" ); } }; class Derive : public Base {public : void print (int ) { log ("in Derive..." ); } }; int main (void ) { Derive *d = new Derive (); d->print (1 ); d->anotherPrint (); d->print (); return 0 ; }
虚函数底层实现 参考陈皓的博客C++虚函数表解析
纯虚函数 纯虚函数:在函数声明后添加=0,将函数声明为纯虚函数。 含有一个或多个纯虚函数的类是抽象基类。不能创建抽象基类实例。 纯虚函数前有virtual后有=0修饰 ,缺一不可。
1 2 3 4 class Abstract {public : virtual void printHello () = 0 ; };
抽象基类中的纯虚函数可以实现,也可以不实现。
虚析构 应为多态基类声明virtual析构函数。详细见《Effective C++》条款07。 当delete掉一个基类指针时,如果这个基类指向的对象是子类对象:
子类含有虚析构,那么会先调用子类的虚构函数,再调用基类的虚构函数
子类不含有虚析构,那么只会调用基类的虚构函数。而没有调用子类的析构函数,很有可能会造成内存泄露。
对比有virtual虚构和无virtual虚构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Base {public : ~Base () { log ("in Base dst..." ); } }; class Derive : public Base {public : ~Derive () { log ("in Derive dst..." ); } }; int main (void ) { Derive *pderive = new Derive (); Base *pbase = pderive; delete pbase; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Base {public : virtual ~Base () { log ("in Base dst..." ); } }; class Derive : public Base {public : virtual ~Derive () { log ("in Derive dst..." ); } }; int main (void ) { Derive *pderive = new Derive (); Base *pbase = pderive; delete pbase; return 0 ; }
虚继承 先上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Base {public : Base (int v): var (v) {log ("Base" );} int var; }; class Left : virtual public Base {public : Left (int v): Base (v) {log ("Left" );} }; class Right : virtual public Base {public : Right (int v): Base (v) {log ("Right" );} }; class Derive :public Left, public Right {public : Derive ():Left (1 ), Right (2 ), Base (3 ) {log ("Derive" );} }; int main (void ) { Derive d; std::cout<<d.var; return 0 ; }
Derive d实例中,只有一个Base子对象。 如果Left和Right不是virtual public继承自Base,那么一个Derive对象中可能会存在两个Base子对象,并且在Derive对象的构造过程中Base构造函数将被调用两次。
Left和Right虚继承自Base,因此Base被称为虚基类。 虚基类的初始化由最底层的子类负责。在此例中是Derive负责。
字符串 C字符串和C++string的size 由于c字符串以\0结尾,同样的字符,c字符串和string字符串的长度并不一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <cstring> #include <string> using namespace std;int main () { char ch[] = "Hello" ; cout<<sizeof (ch) <<endl; cout<<sizeof ("Hello" ) <<endl; string s= "Hello" ; cout<<s.length () <<endl; cout<<s.size () <<endl; }
使用strcpy等函数的隐患 1 char * strcpy (char *destination, char *source) ;
隐患有两点。
source字符串中没有’\0’结束符
destination字符串没有足够的空间存储source字符串
改进以避免隐患
使用strncpy等函数,并保证destination有足够的空间
使用微软的CRT函数1 errno_t strcpy_s (char * dest, size_t numElems, const char * src, size_t count) ;
typedef 1 typedef void (CCObject::*SEL_SCHEDULE) (ccTime) ;
表示SEL_SCHEDULE为一个 参数类型为ccTime,返回类型为void的函数指针类型。
构造&拷贝构造&赋值操作符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <string> void log (const std::string& msg) { std::cout<<msg <<std::endl; } class Base {public : Base (){ log ("in base construct func..." ); } Base (int ) { log ("in base construct func..." ); } Base (const Base& in) { log ("in base copy contruct func..." ); } Base& operator =(const Base&) { log ("in base operator= func..." ); return *this ; } }; class Derive : public Base {}; void func (Base) {}int main (void ) { Base b1; Base b2 = b1; b2 = b1; Derive d; func (b1); func (d); func (1 ); return 0 ; }
函数重载 出现在相同作用域内的两个函数,如果具有相同的名字而形参表不同,则称为函数重载。重载的函数之间不能根据返回值类型来区分,只能根据形参表来区分。
具有相同函数名的两个函数的函数声明:
如果两个函数的返回值类型和形参列表完全相同,则第二个函数声明为第一个函数的重复声明;
如果两个函数的形参列表完全相同,但返回值类型不同,则第二个声明是错误的。
函数重载时选择函数的策略:根据实参。寻找最佳匹配时,实参转换等级以降序排列如下:
精确匹配
通过类型提升实现的匹配
通过标准转换实现的匹配
通过类类类型实现的匹配
那么,当出现不同的实参匹配到不同的重载函数时,则会编译错误。
例如:
1 2 3 4 5 6 void func (int , int ) ;void func (double , double ) ;int i = 1 ;double d = 1.0 ;func (i, d);
显然,根据第一个实参i进行匹配,void func(int, int)
是最佳匹配。根据第二个实参d进行匹配,最佳匹配时void func(double,double)
。 此时,会出现编译错误(ambiguous)。
操作符重载 不能重载的操作符有:「::」,「.」,「*」,「?:」,「sizeof」
重载操作符必须具有一个类类型操作数。用于内置类型的操作符,其含义不能改变。
不能创造新符号。
优先级和结合性是固定的。
不具备短路求值特性。重载操作符不保证操作数的求值顺序。并且,在&&和||的重载中,两个操作数都要进行求值,而且求值顺序不作规定。
重载一元操作符,作为成员函数时没有显示形参,作为非成员函数时就只有一个形参。 重载二元操作符,作为成员函数时有一个形参,作为非成员函数时有两个形参。
作为成员函数的操作符,有一个隐含的this形参,限定为第一个操作数。 当操作符重载定义为非成员函数时,通常必须将它们设置为所操作类的友元。
因此,对于<<和>>操作符重载,需要使用友元:
1 2 3 4 5 6 7 8 9 10 class Base {public : friend std::ostream& operator <<(std::ostream& out, const Base &b); }; std::ostream& operator <<(std::ostream& out, const Base &b) { out<<"Base<<" <<std::endl; return out; }
类型转换
The difference is that in C++ you have various types of casts:
static_cast which is for “safe” conversions;
reinterpret_cast which is for “unsafe” conversions;
const_cast which is for removing a const attribute;
dynamic_cast which is for downcasting (casting a pointer/reference from a superclass to a subclass).
隐式转换 情形1:基本数据类型赋值转换
1 2 3 short a = 128 ;int b; b = a;
情形2:可被单参调用(只有一个参数或多个参数但至少从第二个参数起均带有缺省值)的构造函数或隐式类型转换操作符也会引起隐式类型转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void log (const std::string& msg) { std::cout<<msg <<std::endl; } class Base {public : Base (){ log ("in base construct func..." ); } }; class Other {public : Other () {} Other (Base b) {} }; int main (void ) { Base b; Other o; o = b; return 0 ; }
在C++中,总共有5中显示类型转换方法。
C风格转换 1 2 3 double d;int i;i = (int )d;
static_cast (var) static_cast 很像 C 语言中的旧式类型转换。
它能进行基础类型之间的转换
也能将带有可被单参调用的构造函数或用户自定义类型转换操作符的类型转换
还能在存有继承关系的类指针之间进行转换(即可将基类指针转换为子类指针,也可将子类指针转换为基类指针)。对于有继承关系的实例,基类转换为子类需要子类有能够接受基类参数的构造函数。
还能将 non-const对象指针转换为 const对象指针(注意:反之则不行,那是const_cast的职责。)
注意,以下转换是错误的:
1 2 int *pint = NULL ;char *pchar = static_cast <char *>(pint);
Reason: int needs more memory than what char occupies and the conversion cannot be done in a safe manner. If you still want to acheive this,You can use reinterpret_cast, It allows you to typecast two completely different data types, but it is not safe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <iostream> #include <string> void log (const std::string& msg) { std::cout<<msg <<std::endl; } class Base {public : Base (){ log ("in base construct func..." ); } Base (int ) { log ("in base construct func..." ); } Base (const Base& in) { log ("in base copy contruct func..." ); } Base& operator =(const Base&) { log ("in base operator= func..." ); return *this ; } ~Base () { } }; class Derive : public Base {public : Derive (){ log ("in Derive construct func..." ); } Derive (Base&) { log ("in Derive construct func..." ); } Derive (const Derive& in) { log ("in Derive copy contruct func..." ); } Derive& operator =(const Derive&) { log ("in Derive operator= func..." ); return *this ; } ~Derive () { } }; class Other {public : Other () { log ("in other construct func..." ); } Other (Base&) { log ("in other construct func with Base..." ); } Other (const Other&) { log ("in other copy construct func..." ); } Other& operator =(const Other&) { log ("in other operator= func..." ); return *this ; } }; int main (void ) { double x = 0.01 ; int i = static_cast <int >(x); const int j = static_cast <const int >(i); int k = static_cast <int >(j); const int *pi = &i; Base b; Other o; o = static_cast <Other>(b); Derive d, d1; d = static_cast <Derive>(b); b = static_cast <Base>(d1); Base *pb = new Base (); Derive *pd1 = new Derive (), *pd2; pd2 = static_cast <Derive*>(pb); pb = static_cast <Base*>(pd2); return 0 ; }
dynamic_cast (var) dynamic_cast主要用来在继承体系中的安全向下转型。
向下转型 它能安全地将指向基类的指针转型为指向子类的指针或引用,并获知转型动作成功是否。如果转型失败会返回null(转型对象为指针时)或抛出异常(转型对象为引用时)。dynamic_cast 会动用运行时信息(RTTI)来进行类型安全检查,因此dynamic_cast存在一定的效率损失。
使用dynamic_cast<type> (var)
的条件为var(需要被转型的变量)必须是多态类(有虚函数)或者是子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <string> void log (const std::string& msg) { std::cout<<msg <<std::endl; } class Base {public : virtual void func () {} }; class Derive : public Base {public : virtual void func () {} }; class Other { virtual void func () {} }; int main (void ) { Base *pb1 = new Base (); Derive *pd1 = dynamic_cast <Derive*> (pb1); Derive *pd2 = new Derive (); Base *pb2 = dynamic_cast <Base*> (pd2); Base *pb3 = pd2; Derive *pd3 = dynamic_cast <Derive*> (pb3); Other *po = new Other (); Derive *pd4 = dynamic_cast <Derive*>(po); return 0 ; }
横向转型 向上转型是多态的基础,需不要借助任何特殊的方法,只需用将子类的指针或引用赋给基类的指针或引用即可,当然dynamic_cast也支持向上转型,而其总是肯定成功的。而对于向下转型和横向转型来讲,其实对于dynamic_cast并没有任何区别,它们都属于能力查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Shape { public : virtual ~Shape (); virtual void draw () const = 0 ; }; class Rollable { public : virtual ~Rollable (); virtual void roll () = 0 ; }; class Circle : public Shape, public Rollable { void draw () const ; void roll () ; }; class Square : public Shape { void draw () const ; }; Shape *pShape1 = new Square (); Rollable *pRollable1 = dynamic_cast <Rollable*>(pShape2); Shape *pShape2 = new Circle (); Rollable *pRollable2 = dynamic_cast <Rollable*>(pShape2);
const_cast const_cast可去除对象的常量性(const),它还可以去除对象的易变性(volatile)。const_cast的唯一职责就在于此,若将const_cast 用于其他转型将会报错。
reinterpret_cast reinterpret_cast用来执行低级转型,如将执行一个int的指针强转为 int。其转换结果与编译平台息息相关,不具有可移植性。 reinterpret_cast 常用的一个用途是转换函数指针类型,即可以将一种类型的函数指针转换为另一种类型的函数指针,但这种转换可能会导致不正确的结果。
signed和unsigned 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> using namespace std;int main () { unsigned int ui; unsigned char uc; signed int si; signed char sc; si = -1 ; ui = si; sc = -1 ; uc = sc; ui = 0xffe0 ; uc = ui; sc = ui; si = -129 ; sc = si; uc = si; uc = -1 ; ui = uc; uc = 0xe0 ; si = uc; sc = 0xe0 ; ui = sc; sc = 0xe0 ; si = sc; }
内存类型 程序占用的内存主要分为:
栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。C++中的new和malloc申请的内存都属于这一块。
全局区(静态区)(static),未初始化的全局变量和静态变量的存储是放在一块的,初始化的全局变量,静态变量和const变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。
文字常量区—常量字符串就是放在这里的。程序结束后由系统释放
程序代码区—存放函数体的二进制代码。
栈是由高地址向低地址扩展的数据结构,是一块连续的内存的区域。但是分配在栈中的数组,下标小的元素处于低地址。
区分以下两种情况:
1 2 char *pchar = "Hello World" ; char charArray[] = "Hello World" ;
内存管理 new和delete
new operator & delete operator new操作符和delete操作符像sizeof等操作符一样,语言内置,不能进行改变。 new操作符做的工作:调用操作符new(new函数,与new操作符区别)分配内存,调用对象的构造函数等进行初始化。 delete操作符的工作:调用对象析构函数,操作符delete释放内存。
operator new & operator delete operator new和operator delete类同C语言中的malloc和free函数,用于申请内存和释放内存。并不会调用析构函数。 operator new函数,一般这样子声明:
1 void * operator new (size_t size) ;
operator new和operator delete是允许程序员根据自己的需要去重新实现的,而new operator和delete operator则不可以。
new[] & delete[] new[]和delete[]用于申请数组和释放数组。delete[]只是去释放数组本身的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;class Base {public : ~Base () { cout<<"in Base dst" <<endl; } }; int main () { Base *baseArray = new Base[2 ]; delete [] baseArray; Base **array2 = new Base*[2 ]; array2[0 ] = new Base (); array2[1 ] = new Base (); delete [] array2; }
不要通过基类数组delete来delete子类数组。 以下做法是不合理的:
1 2 3 4 class Base {};class Derive : public Base {};Base *pBase = new Derive[3 ]; delete []pBase;
特别的,当析构函数为virtual,并且sizeof(Base)和sizeof(Derive)不一样时,运行时会出错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Base {public : int a; virtual ~Base () {log ("in Base dst" );} }; class Derive :public Base {public : int b; virtual ~Derive () {log ("in Derive dst" );} }; int main (void ) { Derive *pDerive = new Derive[2 ]; Base *pBase = pDerive; cout<<"int size: " <<sizeof (int ) <<endl; cout<<"pointer size: " <<sizeof (int *) <<endl; cout<<"Base size: " <<sizeof (Base) <<endl; cout<<"Derive size: " <<sizeof (Derive) <<endl; delete []pBase; }
placement new & placement delete placement new的作用是在已经分配好的内存上分配内存。placement new的声明定义如下:1 2 3 4 void * operator new (size_t size, void *pLocation) { return pLocation; }
内存对齐 由于计算机底层内存传输是以字长(32bit机上为4bytes)为单位进行传输的。 例如:
1 2 3 4 5 6 struct X { char c1; char c2; char c3; }; struct X xarray[2 ];
如果不进行内存对齐,对于xarray1 ,存在于第一个字长的最后一个byte和第二个字长的前两个byte。 因此,需要访问xarray1 时,需要进行传输两个字长,降低效率。 内存对齐后,则不会有此类问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 struct Data { int a; char c; int b; }; struct Data1 { int a; char c; int b; void print () { cout<<"print" <<endl; } }; struct Data2 { int a; char c; int b; char cc; }; struct Data3 { int a; char c; char cc; int b; }; class Data4 { int a; char c; char cc; int b; }; class Data5 { int a; char c; char cc; int b; virtual ~Data5 () {} }; int main (void ) { cout<<"size of Data: " <<sizeof (struct Data) <<endl; cout<<"size of Data1: " <<sizeof (struct Data1) <<endl; cout<<"size of Data2: " <<sizeof (struct Data2) <<endl; cout<<"size of Data3: " <<sizeof (struct Data3) <<endl; cout<<"size of Data4: " <<sizeof (class Data4) <<endl; cout<<"size of Data5: " <<sizeof (class Data5) <<endl; }
求值顺序
函数调用顺序 以下例子在我机子上(win7 & gcc),函数调用时参数求值顺序从后往前的。 考虑到函数调用时,参数的压栈顺序为,先压最后一个参数,接着倒数第二个…最后压第一个参数。 大部分情况下,求值顺序为从后往前。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> int func1 () { std::cout<<"in func1" <<std::endl; return 1 ; } int func2 () { std::cout<<"in func2" <<std::endl; return 2 ; } int func (int , int ) { std::cout<<"in func" <<std::endl; return 3 ; } int main (void ) { func (func1 (), func2 ()); func (func2 (), func1 ()); return 0 ; }
基本运算的求值
1 2 int a = 1 ;int b = a + (a++);
以上例子的行为未定义的。C++标准并不明确求职顺序。
&&和||求值 &&和||有最短路径求值。 (expression A) && (expression B):对A求值,若A为true则对B求值;若A为false则直接返回false而不对B求值 (expression A) || (expression B):对A求值,若A为true则直接返回true而不对B求值;若A为false则对B求值
引用 引用是变量的另外一个名字。
1 2 3 4 5 6 int main (void ) { int a = 1 ; int &refa = a; refa = 100 ; cout<<a; }
注意:
引用必须在定义的同时初始化
外部(extern)引用不必给初值 extern int *refa
;
引用初始化后不能再改为其他变量的引用
引用的地址后被引用变量的地址相同。 int a; int &refa = a
; 那么&refa和&a得到的地址相同。
引用和指针一样,可产生多态的效果。
引用的size和指针的size一样。
不能返回局部变量的引用。 返回局部变量的引用是不安全的。局部变量在程序离开作用于后即会被回收。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int & hello () { int local = 131 ; return local; } void test () { int x = 100 ; } int main (void ) { int &x = hello (); std::cout<<x <<std::endl; test (); std::cout<<x <<std::endl; }
但是如果上面的int &x = hello()
改为int x = hello()
,那么两次输出的结果都为131。 原因是返回对local的引用,local的值拷贝到了变量x中。
不能返回函数内部new分配的内存的引用。 被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
可以返回类成员的引用,但最好是const,否则类内成员变量可以被任意更改,破坏了封装性。
友元 声明友元后,友元能访问类的protected和private成员。
1 2 3 4 5 6 7 8 9 class Base { friend class Frnd ; private : int m_x; }; class Frnd {};class Derive : public Base {};
此时,Frnd类能访问m_x。 但是friend不能继承 ,Frnd不能访问Derive的protected和private成员。 并且,如果一个类希望另外的基类和其子类作为友元,必须分别声明。 例如,如果Frnd希望Base和Derive都能访问自己的protected和private成员,必须要
1 2 friend class Base ; friend class Derive ;
位运算 位运算操作符的优先级比+和-低,作运算时要特别注意是否要添加括号。
按位与 & 可以实现清零,取特定位,保留特定位的效果。
按位或 | 通常应用于将某些位设定为1
按位异或 ^ 即 0 ^ 0 = 0, 0 ^ 1 = 1, 1 ^ 0 = 1, 1 ^ 1 = 0
使特定位翻转,例如 01111010 ^ 00001111 = 01110101
与0作异或,保留原值
清零,相同值的变量作异或运算得0
利用上一性质,可以得到,变量a和b作两次异或运算结果为a,及时 a ^ b ^ b结果为a 因此,可以实现无临时变量交换值:
1 2 3 a = a ^ b; b = a ^ b; a = a ^ b;
按位取反 ~ (一元运算负) 对于有符号数,易得到 a + (~a) = -1;
位运算赋值运算符 &=, |=, ^=, >>=, <<=
Rule of Three
当你需要自己实现析构函数时(通常是因为指针需要delete),通常也需要手动实现copy构造和operator=(通常是为深拷贝)。
手动实现copy构造,operator=,析构的其中一个函数时,需要考虑另外两个函数是否应该手动实现。
当类中含有非静态引用或者非静态const变量时必须要自己实现operator=
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Base {public : const int constVar; int &refVar; Base (int rv, int cv):refVar (rv),constVar (cv) {} }; int main (void ) { int v1 = 1 , v2 = 2 ; Base b1 (v1, v2) ; int v3 = 3 , v4 = 4 ; Base b2 (v3, v4) ; b1 = b2; }
继承 公有继承(public)、私有继承(private)、保护继承(protected)是常用的三种继承方式。 下面列出三种不同的继承方式的基类特性和派生类特性。
public
protected
private
公有继承
public
protected
不可见
私有继承
private
private
不可见
保护继承
protected
protected
不可见
注意以下情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> class Base {protected : int protectedVar; private : int privateVar; }; class Derive : public Base {public : void print () { protectedVar = 1 ; } void func (Base& base) { } void func2 (Derive& derive) { derive.protectedVar; } };
switch妙用 参考Using {} in a case statement. Why?
case语句中不使用{}可能会导致编译错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int getNum (int a) { switch (a) { case 0 : int x = 1 ; return x+2 ; break ; case 1 : int x = 2 ; return x+3 ; break ; } return 1 ; } int getNum (int a) { switch (a) { case 0 : int x = 1 ; return x+2 ; break ; case 1 : x = 2 ; return x+3 ; break ; } return 1 ; }
给每个case加上{}便不会编译错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int getNum (int a) { switch (a) { case 0 : { int x = 1 ; return x+2 ; break ; } case 1 : { int x = 2 ; return x+3 ; break ; } } return 1 ; }
原因是:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. 不能通过case和goto等语句跳过一个含有初始化的声明。switch,case语句实际上是由goto合label实现。
1 2 3 4 5 6 7 8 int main (void ) { goto labely; labelx: int a = 1 ; labely: std::cout<<a; }
但是可以通过case和goto语句跳过一个不含有初始化的声明。 例如以下两个例子都是没错的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int main (void ) { goto labely; labelx: int a; labely: a = 11 ; std::cout<<a; } int getNum (int a) { switch (a) { case 0 : int x; x = 1 ; return x+2 ; break ; case 1 : x = 3 ; return x+3 ; break ; } return 1 ; }
那么可以充分利用switch语句的特性,将switch语句和while循环结合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void print (int a) { int c = 2 ; switch (a) { case 1 : do { std::cout<<"case 1" <<std::endl; case 2 : std::cout<<"case 2" <<std::endl; case 3 : std::cout<<"case 3" <<std::endl; default : std::cout<<"default" <<std::endl; } while (--c); } } int main (void ) { print (2 ); }