C++部分关键字总结
auto
c++11引入的auto关键字实现类型退到,通过这个关键字不仅能方便地获取复杂的类型,而且还能简化书写,提高编码效率。
auto x = 5; //ok auto pi = new auto(1); //ok const auto *v = &x, u = 6; //ok auto int r; //error:auto不在表示存储类型指示符,这也是新更改的特性 auto str; //error:无法推导出str类型
note:v与u的推导需要注意两个小地方
1)虽然经过前面const auto *v = &x 的推导,auto的类型可以确定为int,但是u仍然必须要写后面的“=6”,否则编译不通过。
2)u的初始化不能使编译器推导产生二义性。ep:u = 6改为 u = 6.0, 编译器会报错。
auto并不代表实际的类型声明,仅仅是一个类型声明的“占位符”。
使用auto声明的变量必须马上初始化,让编译器推导出它的实际类型,并在编译时将auto占位符替换为真正的类型。
在c++11之前,auto 表示“具有自动存储期的局部变量”,不过其实它在这方面的作用不大,
auto int i = 1; 对这个我们再熟悉不过了。不写auto也是一样的效果就是为了跟static int j = 1;区分开来而已。
auto推导可以和指针,引用结合起来使用,还可以带上const,volatile限定符。
int x = 1; const auto e =x; //const int auto f = e; //int const auto& g = x; //const int& auto& h= g; //const int&
从上面的例子不难看出:
1)当不声明为指针和引用时,auto的推导结果和初始化表达式抛弃引用和cv限定符后类型一致。
2)当声明为指针或者引用时,auto的推导结果将保持初始化表达式的cv属性。
decltype
decltype关键字,用来在编译时推导出一个表达式的类型。语法格式:
decltype(exp)
int x = 0; decltype(x) y = 1; // y->int const int& i = x; //i->const int & decltype(i) j = y; //j->const int & const decltype(y) * p =&z; // p->const int * decltype(x) * pi = &x; //*pi->int decltype(pi)* pii = &pi //*pii->int *
从上面的例子不难看出decltype的推导规则:
1)exp是标示符,雷访问表达式,decltype(exp)和exp类型一致
2)exp是函数调用,decltype(exp)和返回值的类型一致
3)若exp是一个左值,则decltype(exp)是exp类型的左值引用,否则和exp类型一致。
decltype与引用
int i = 42; int *p = &i: decltype(*p) c;//error:c是int &必须初始化
所以如果表达式是解引用的操作,则decltype得到引用类型。解引用可以得到指针所指的对象,而且还能给这个对象赋值。
decltype与auto还有一个重要的区别:decltype与表达式的形势密切相关。对于decltype所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号是不一样的。也就是说decltype使用一个不加括号的变量,则得到的结果就是该变量的类型。而加上一层或者多层括号的是作为一个特殊的左值表达式。得到的是引用类型。
decltype(i) = e; //ok decltype((i)) = e; //error:e为引用类型必须初始化
explicite
explicit使用注意事项:
*explicit 关键字只能用于类内部的构造函数声明上。
*explicit 关键字作用于单个参数的构造函数。
在c++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
explicit关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不能再重复它。
如果一个类或结构存在多个构造函数时,explicit 修饰的那个构造函数就是默认的
当构造函数被声明为explicit时,编译器将不使用它作为转换操作符。
explicit 关键字作用于单个参数的构造函数。
通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。
inline
1) inline表示内联,即在函数调用出将函数内联地展开。
2) inline既可以出现在类定义内部,也可以出现在外部。当在外部出现时必须定义在类定义的头文件中,因为调用函数时需要看到函数是什么样子。
3)inline修饰的函数是在编译时期。
constexptr
这个constexptr函数指能用于常量表达式的函数。定义为constexptr的函数的方法与其它函数类似。不过要遵循:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句:
constexptr int new_sa() {return 42;} constexptr int foo = new_sa();//ok
我们把new_sz定义成无参数的constexpr函数。因为编译器能在程序编译时验证new_sz函数的返回的是常量表达式,所有可以用new_sz函数初始化constexpr类型的变量foo。
执行该初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数被隐式的定义为内联函数。
constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr函数中可以有空语句、类型别名以及using声明。
我们允许constexpr函数的返回值并非一个常量:
//如果arg是常量表达式,则scale(arg)也是常量表达式
constexpr size_t scale(size_t cnt) {return new_sz()*cnt;}
当scale的实参是常量表达式时,它的返回值也是常量表达式:反之则不然:
int arr[scale(2)]; //正确:scale(2)是常量表达式
int i=2; //i不是常量表达式
int a2[scale(i)]; //错误:scale(i)不是常量表达式
如上例所示,当我们给scale函数传入一个形如字面值2的常量表达式时,它的返回类型也是常量表达式。此时,编译器用相应的结果值替换对scale函数的调用。
如果我们用一个非常量表达式调用scale函数,比如int类型的对象i,则返回值是一个非常量表达式。当把scale函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式,编译器将发出错误信息。
constexpr函数不一定返回常量表达式
我也有点云里雾里的。大家看着理解吧。
把内联函数和constexpr函数放在头文件内
和其他函数不一样,内联函数和constexpr函数可以再程序中多次定义。毕竟,编译器要想展开函数仅有函数声明是不够的,还需要函数的定义。不过,对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须定义一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中。
friend
1) 类的friend函数可以访问类的private和protected成员。
2) friend关系不能继承,基类的友元对子类的成员没有特殊访问权限,如果基类被授予友元关系,则只有基类具有特殊访问权限,其派生类不能访问授予友元关系的类。
3) 派生类的friend函数可以访问派生类的一切变量,包括从基类继承下来的protected域中的变量,但对父类来说它不是friend。也就是说friend和类本身的访问权是100%。
4) friend关键字只能出现在类定义内部,不可出现在外部。
#include #include class point { public: point(double xx, double yy) { x=xx; y=yy; } void getxy(); friend double distance(point &a, point &b); private: double x, y; }; double distance(point &a, point &b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx*dx+dy*dy); }
static
在c++中此关键字用途更多。
1) static类成员属于整个类,不属于某个类对象。
2) static成员遵循正常的公有/私有访问规则。
3) 对于public static成员可以通过类名或对象名访问,对于private static成员则不可,须按照正常private成员访问规则进行访问。
(4) static成员函数可以访问static成员函数和变量,不可访问非static成员和函数,因为static成员是类的,而非static是对象的,类的产生先于对象,怎么能在对象为产生之间就调用它的数据呢。
5) 非static成员函数既可以访问static成员又可以访问非static成员。
6) 可以通过类名直接调用public static成员,或者通过对象名、引用或指针间接调用。注意此处指的是public static成员,不是private。
7) static成员函数没有this指针,因为this指向的是对象。
8) static成员函数不能声明为const或虚函数,因为此二者都是针对对象而言,此外static函数实际上是”加上了访问控制的全局函数”,全局函数当然没有虚函数。但是static变量可以声明为const。
9) static只用于类内声明,在类外定义时不能加static。
10) static变量的初始化只能放在源文件中,如果放在头文件中可能会被重复定义。
11) 在对static变量进行初始化时,可以忽略任何存取权限的束缚,例如:
double classa::m_a = 0.05; (注意假设classa::m_a是private, 则除了在定义static时可以这样写外,其它地方均不得如此)
12) static成员变量不能再类内初始化,必须在类外定义时初始化(在源文件中),但是有个例外,const static成员可以在类内初始化,同时仍必须在类外进行定义,定义时无需加static,无需指定初始值,但必须加const修饰符。[这一条引自c++ primer,4th edition, p401,但是上机实验证明,如果已经在类内初始化,则就不须在类外定义,否则产生多重定义的变异错误,不知道是不是编译器的问题,我用的是vs2005]
13) static成员可以被继承,父类的static变量和函数在派生类中依然可用,但是受访问控制,而且对static变量来说,派生类和父类中的static变量是共用空间的,这点在利用static变量进行引用计数时要注意。
静态成员函数不能够使用const和volatile修饰。并且静态成员函数只能访问静态成员变量,在静态成员函数中不能使用this。
register
这个关键字命令编译器尽可能的将变量存在cpu内部寄存器中而不是通过内存寻址访问以提高效率。
volatile
volatile的本意是“易变的”,volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被寄存。
volatile int i=10; int a = i; 。。。//其他代码,并未明确告诉编译器,对i进行过操作 int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
const_cast
从类中移除 const、volatile特性。
指向任何对象类型的指针或指向数据成员的指针可显式转换为完全相同的类型(const、volatile 和 __unaligned 限定符除外)。 对于指针和引用,结果将引用原始对象。 对于指向数据成员的指针,结果将引用与指向数据成员的原始(未强制转换)的指针相同的成员。 根据引用对象的类型,通过生成的指针、引用或指向数据成员的指针的写入操作可能产生未定义的行为。
您不能使用 const_cast 运算符直接重写常量变量的常量状态。
const_cast 运算符将 null 指针值转换为目标类型的 null 指针值。
const
const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个类型修饰符。const可以用于定义常量,可以限定函数的引用参数(因为传值的参数根本不用限定),可以限定函数返回值为引用的情况。还有一个用法是修饰类的成员函数。这样情况下,在类内的声明和类外的定义都要加上const。
还有一种情况是,声明类的const成员变量的时候,如何进行初始化。
在这种情况下,由于常量不能修改,所以只能在构造函数的初始化列表中进行复制初始化。如果同时声明为了static时可以在类外进行初始化,但此时不能加static关键字。
virtual
在基类中被定义为virtual的函数,派生类重载该函数不需要再次显示说明该函数是virtual的。
inline, static, constructor三种函数都不能带有virtual关键字。
inline是编译时展开,必须有实体;
static属于class自己的,也必须有实体;
virtual函数基于vtable(内存空间),constructor函数如果是virtual的,调用时也需要根据vtable寻找,但是constructor是virtual的情况下是找不到的,因为constructor自己本身都不存在了,创建不到class的实例,没有实例,class的成员(除了public static/protected static for friend class/functions,其余无论是否virtual)都不能被访问了。
static_cast
该运算符把expssion转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
1)用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
2)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
3)把空指针转换成目标类型的空指针,把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expssion的const、volitale、或者__unaligned属性。
dynamic_cast
dynamic_cast’只用于对象的指针和引用。当用于多态类型时,它允许任意的隐式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(注:即隐式转换的相反过程),dynamic_cast会检查操作是否有效。也就是说,它会检查转换是否会返回一个被请求的有效的完整对象。
检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为null。
class base { virtual dummy() {} }; class derived : public base {}; base* b1 = new derived; base* b2 = new base; derived* d1 = dynamic_cast(b1); // succeeds derived* d2 = dynamic_cast(b2); // fails: returns 'null' 如果一个引用类型执行了类型转换并且这个转换是不可能的,一个bad_cast的异常类型被抛出: 代码: class base { virtual dummy() {} }; class derived : public base { }; base* b1 = new derived; base* b2 = new base; derived d1 = dynamic_cast(b1); // succeeds derived d2 = dynamic_cast(b2); // fails: exception thrown
reinterpret_cast
reinterpret_cast’转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型。反之亦然。(译注:是指针具体的地址值作为整数值?)
这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。
如果情况是从一个指针到整型的拷贝,内容的解释是系统相关的,所以任何的实现都不是方便的。一个转换到足够大的整型能够包含它的指针是能够转换回有效的指针的。\
说白了就是强制类型转换。
class a {}; class b {}; a * a = new a; b * b = reinterpret_cast(a); 'reinterpret_cast'就像传统的类型转换一样对待所有指针的类型转换。