簡(jiǎn)介
C++98/03的設(shè)計(jì)目標(biāo):
一、比C語(yǔ)言更適合系統(tǒng)編程(且與C語(yǔ)言兼容)。
二、支持?jǐn)?shù)據(jù)抽象。
三、支持面向?qū)ο缶幊獭?br>
四、支持泛型編程。
(C++模板使得C++近乎成為了一種函數(shù)式編程語(yǔ)言,而且使得C++程序員擁有了模板元編程的能力。)
相比之下,C++11的整體設(shè)計(jì)目標(biāo)如下:
一、使得C++成為更好的適用于系統(tǒng)開發(fā)及庫(kù)開發(fā)的語(yǔ)言。
二、使得C++成為更易于教學(xué)的語(yǔ)言(語(yǔ)法更加一致化和簡(jiǎn)單化)。
三、保證語(yǔ)言的穩(wěn)定性,以及和C++03及C語(yǔ)言的兼容性。
(如果說C++11只是對(duì)C++語(yǔ)言做了大幅度的改進(jìn),那么就很有可能錯(cuò)過了C++11精彩的地方。就是要做到,讀完這本書之后,只需要看一眼,就可以說出代碼是C++98/03的,還是C++11的)。
我想要的,就是C++11為程序員創(chuàng)造了很多更有效、更便捷的代碼編寫方式,程序員可以用簡(jiǎn)短的代碼來完成C++98/03中同樣的功能,簡(jiǎn)單到你會(huì)說"竟然這么簡(jiǎn)單"。比起C++98/03,C++11大大縮短了代碼編寫量,最多可以將代碼縮短30%~80%。
C++11相對(duì)C++98/03有哪些顯著的增強(qiáng)呢?包括以下幾點(diǎn):
一、通過內(nèi)存模型、線程、原子操作等來支持本地并行編程(Native Concurrency)。
二、通過統(tǒng)一初始化表達(dá)式、auto、declytype、移動(dòng)語(yǔ)義等來統(tǒng)一對(duì)泛型編程的支持。
三、通過constexpr、POD(概念)等更好地支持系統(tǒng)編程。
四、通過內(nèi)聯(lián)命名空間、繼承構(gòu)造函數(shù)和右值引用等,以更好地支持庫(kù)的構(gòu)建。
(C++11像是個(gè)恐怖的“編程語(yǔ)言范型聯(lián)盟”。利用它不僅僅可以寫出面向?qū)ο蟮拇a,也可以寫出過程式編程語(yǔ)言代碼、泛型編程語(yǔ)言代碼、函數(shù)式編程語(yǔ)言代碼、元編程編程語(yǔ)言代碼,或者其他。多范型的支持使得C++11語(yǔ)言的“硬實(shí)力”幾乎在編程語(yǔ)言中“無(wú)出其右”)
程序員需要抽象出的不僅僅是對(duì)象,還有一些其他的概念,比如類型、類型的類型、算法、甚至是資源的生命周期,這些實(shí)際上都是C++語(yǔ)言可以描述的。在C++11中,這些抽象概念常常被實(shí)現(xiàn)在庫(kù)中,其使用將比在C++98/03中更加方便,更加好用。
(C++11是一種所謂的“輕量級(jí)抽象編程語(yǔ)言”)總的來說,靈活的靜態(tài)類型、小的抽象概念、絕佳的時(shí)間與空間運(yùn)行性能,以及 與硬件緊密結(jié)合工作的能力 都是C++11突出的亮點(diǎn)。
WG21更專注的特性(我比較關(guān)注的)
1、更傾向于使用庫(kù)而不是擴(kuò)展語(yǔ)言來實(shí)現(xiàn)特性。
2、更傾向于通用的而不是特殊的手段來實(shí)現(xiàn)特性。
3、增強(qiáng)代碼執(zhí)行性能和操作硬件的能力。
4、開發(fā)能夠改變?nèi)藗兯季S方式的特性
5、融入編程現(xiàn)實(shí)
Mayers根據(jù)C++11的使用者是類的使用者,還是庫(kù)的使用者,或者特性是廣泛使用的,還是庫(kù)的增強(qiáng)的來區(qū)分各個(gè)特性。具體地,可以把特性分為以下幾種:
A 類作者需要的(class writer, 簡(jiǎn)稱為 “類作者”)
B 庫(kù)作者需要的(library writer, 簡(jiǎn)稱為 “庫(kù)作者”)
C 所有人需要的(everyone, 簡(jiǎn)稱為 “所有人”)
D 部分人需要的(everyone else, 簡(jiǎn)稱為 “部分人”)
保證穩(wěn)定性和兼容性
保持與C99兼容
(這里有些庫(kù)不是特別懂,我們慢慢來)
#c++11 測(cè)試代碼
#include <iostream>
#include <thread>
using namespace std;
void my_thread()
{
puts("hello, world");
}
int main(int argc, char *argv[])
{
std::thread t(my_thread);
t.join();
system("pause");
return 0;
}
thread是C++11的線程類,頭文件include <thread>。此程序能正常輸出,說明配置C++11成功。
c++11 對(duì)C99宏的一些支持:
STDC_HOSTED: 如果編譯器的目標(biāo)系統(tǒng)環(huán)境中包含完整的標(biāo)準(zhǔn)C庫(kù),那么這個(gè)宏就定義為1,否則宏的值為0
STDC: C編譯器通常用這個(gè)宏的值來表示編譯器的實(shí)現(xiàn)是否和C標(biāo)準(zhǔn)一致。C++11標(biāo)準(zhǔn)中這個(gè)宏是否定義以及定義成什么值由編譯器來決定。
(其他兩個(gè)值,好像在我的Dev-C++ 5.9.2中不太支持)
#include <iostream>
using namespace std;
int main(){
cout<<"Standard Clib:"<<__STDC_HOSTED__<<endl;//Standard Clib:1
cout<<"Standard C:"<<__STDC__<<endl;//Stand C:1
// cout<<"C Stardard version:"<<__STDC_VERSION__<<endl;
// cout<<"ISO/IEC"<<__STDC_ISO_10646__<<endl;//ISO/IEC 200009
}
//編譯選項(xiàng):g++ -std=c++11 2-1-1.cpp
預(yù)定義宏對(duì)于多目標(biāo)平臺(tái)代碼的編寫通常具有重大意義。通過以上的宏,程序員通過使用#ifdef/#endif等預(yù)處理命令,就可使得平臺(tái)相關(guān)代碼只適合于當(dāng)前平臺(tái)的代碼上編譯,從而在同一套代碼中完成對(duì)多平臺(tái)的支持。(通過這個(gè)預(yù)處理命名,就可以讓一些頭文件中的代碼在只適合當(dāng)前平臺(tái)的情況下進(jìn)行編譯。)
相關(guān)博客:
[C++中#if #ifdef的作用][1]
例如:
#include <iostream>
using namespace std;
int main(int argc,char** argv)
{
cout << "__FILE__ = " << __FILE__ << endl;
cout << "__DATE__ = " << __DATE__ << endl;
cout << "__TIME__ = " << __TIME__ << endl;
cout << "__LINE__ = " << __LINE__ << endl;
#ifdef __cplusplus
cout<<"在此環(huán)境中可以編緝和調(diào)試標(biāo)準(zhǔn)C++程序。"<<endl;
#endif
#ifdef __STDC__
cout<<"在此環(huán)境中可以編緝和調(diào)試標(biāo)準(zhǔn)C程序。"<<endl;
#endif
return EXIT_SUCCESS;
}
值得注意的是,與所有的預(yù)定義宏相同的,如果用戶重定義(#define)或#undef了預(yù)定義的宏,那么后果是“未定義”的。因此在代碼編寫中,程序員應(yīng)該注意避免自定義宏與預(yù)定義宏同名的情況。
(那么問題是:什么是預(yù)定義宏,這個(gè)預(yù)定義宏有什么用)
答案:
預(yù)定義宏,就是事先已經(jīng)定義好的宏。一般分為兩類:標(biāo)準(zhǔn)預(yù)定義宏,編譯器預(yù)定義宏。有兩個(gè)特性:
- 無(wú)需提供他們的定義,就可以直接使用。
- 預(yù)定義宏沒有參數(shù),且不可被重定義。
A:標(biāo)準(zhǔn)預(yù)定義宏(Standard Predefined Macros)
標(biāo)準(zhǔn)預(yù)定義宏由相關(guān)語(yǔ)言標(biāo)準(zhǔn)指定。因此所有該標(biāo)準(zhǔn)的編譯器都可以使用這些宏。ANSI C指定了以下預(yù)定義宏:
- _FILE_ 在源文件中插入當(dāng)前源文件名;
- _LINE_ 在源代碼中插入當(dāng)前源代碼行號(hào);
- _DATE_ 在源文件中插入當(dāng)前的編譯日期;
- _STDC_ 當(dāng)要求程序嚴(yán)格遵循ANSI C標(biāo)準(zhǔn)時(shí)該標(biāo)識(shí)被賦值為1,表明是標(biāo)準(zhǔn)的C程序;
- _TIME_ 在源文件中插入當(dāng)前編譯時(shí)間;
- _TIMESTAMP_ The date and time of the last modification of the current source file, expressed as a string literal in the form Ddd Mmm Date hh:mm:ss yyyy, where Ddd is the abbreviated day of the week and Date is an integer from 1 to 31.
#include <iostream>
using namespace std;
int main(int argc,char** argv)
{
cout << "__FILE__ = " << __FILE__ << endl;
cout << "__DATE__ = " << __DATE__ << endl;
cout << "__TIME__ = " << __TIME__ << endl;
cout << "__LINE__ = " << __LINE__ << endl;
#if defined(__cplusplus)
cout<<"在此環(huán)境中可以編緝和調(diào)試標(biāo)準(zhǔn)C++程序。"<<endl;
#endif
#if defined(__STDC__)
cout<<"在此環(huán)境中可以編緝和調(diào)試標(biāo)準(zhǔn)C程序。"<<endl;
#endif
return EXIT_SUCCESS;
}
![QQ截圖20151107172832.jpg-72.1kB][2]
(C99在_FILE、_LINE的之外,引入了_func_與之配合。注意func并不是宏)
具體參考如下官方文檔:
[http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html][3]
[http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html#Function-Names][4]
B:編譯器預(yù)定義宏(GNU-, Microsoft-Specific Predefined Macros)
這部分的宏是由編譯器指定的,是對(duì)標(biāo)準(zhǔn)預(yù)定義宏的拓展。如GCC和VC++都有自己預(yù)定于的宏。
具體參考如下官方文檔:
[http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros][5]
[http://msdn.microsoft.com/en-us/library/b0084kay][6]
ps: 編譯器預(yù)定義宏,也有部分實(shí)際上不是真正的宏。拿VC++來說,可以驗(yàn)證的,_MSC_VER是宏,FUNCDNAME不是宏。
(預(yù)定義宏是很有用的,比如你要輸出日期,時(shí)間,文件名,函數(shù)名等等,都要用到它。預(yù)定義宏的使用比較簡(jiǎn)單,網(wǎng)上可以找到很多介紹文章,特別是標(biāo)準(zhǔn)預(yù)定義宏。)
個(gè)人理解,就像我寫PHP一樣,會(huì)有些可以直接調(diào)用的方法,這些方法基本上是PHP已經(jīng)定義過的。
很多現(xiàn)實(shí)的編譯器都支持C99標(biāo)準(zhǔn)中的_func_預(yù)定義標(biāo)識(shí)符功能,其基本功能就是返回所在函數(shù)的名字。
#include <string>
#include <iostream>
using namespace std;
const char *hello(){return __func__;}
const char *world(){return __func__;}
int main(){
cout<<hello()<<","<<world()<<endl;//hello,world
}
//編譯選項(xiàng):g++ -std=c++11 2-1-1.cpp
事實(shí)上,按照標(biāo)準(zhǔn)定義,編譯器會(huì)隱式地在函數(shù)的定義之后定義_func_標(biāo)識(shí)符。比如上述例子中的hello函數(shù),其實(shí)際的定義等同于如下代碼:
const char*hello(){
static const char*__func__="hello";//定義了一個(gè)靜態(tài)的常量。
return __func__;
_func_ 預(yù)定義標(biāo)識(shí)符對(duì)于輕量級(jí)的調(diào)試代碼具有十分重要的作用。而在C++11中,標(biāo)準(zhǔn)甚至允許其使用在類或者結(jié)構(gòu)體中。(問題是:_func_怎樣去調(diào)試代碼)
#include <string>
#include <iostream>
using namespace std;
struct TestStruct{
TestStruct(): name(__func__){// name屬性用__func__初始化,構(gòu)造函數(shù)體是空,推薦這種寫法。
//name = __func__; 也可以寫在函數(shù)體里面。
}
const char*name;
};
//const char *hello(){return __func__;}
//const char *world(){return __func__;}
int main(){
TestStruct ts;
cout<<ts.name<<endl;//Teststruct
}
//編譯選項(xiàng):g++ -std=c++11 2-1-1.cpp
在結(jié)構(gòu)體的構(gòu)造函數(shù)中,初始化成員列表使用func預(yù)定義標(biāo)識(shí)符是可行的,其效果跟在函數(shù)中使用一樣。不過將fun標(biāo)識(shí)符作為函數(shù)參數(shù)的默認(rèn)值是不允許的,如下例所示:
void FuncFail(string func_name=__func__){}; //無(wú)法通過編譯
(這是由于在參數(shù)聲明時(shí),func還未被定義)
問題:在結(jié)構(gòu)體中是否可以聲明函數(shù)?
答案:
C++中結(jié)構(gòu)體可以定義一個(gè)函數(shù)
C中的結(jié)構(gòu)體和C++中結(jié)構(gòu)體的不同之處:在C中的結(jié)構(gòu)體只能自定義數(shù)據(jù)類型,結(jié)構(gòu)體中不允許有函數(shù),而C++中的結(jié)構(gòu)體可以加入成員函數(shù)。
C++中的結(jié)構(gòu)體和類的異同:
一、相同之處:結(jié)構(gòu)體中可以包含函數(shù);也可以定義public、private、protected數(shù)據(jù)成員;定義了結(jié)構(gòu)體之后,可以用結(jié)構(gòu)體名來創(chuàng)建對(duì)象。但C中的結(jié)構(gòu)體不允許有函數(shù);也就是說在C++當(dāng)中,結(jié)構(gòu)體中可以有成員變量,可以有成員函數(shù),可以從別的類繼承,也可以被別的類繼承,可以有虛函數(shù)。
二、不同之處:結(jié)構(gòu)體定義中默認(rèn)情況下的成員是public,而類定義中的默認(rèn)情況下的成員是private的。類中的非static成員函數(shù)有this指針,類的關(guān)鍵字class能作為template模板的關(guān)鍵字 即template<class T> class A{}; 而struct不可以。
實(shí)際上,C中的結(jié)構(gòu)體只涉及到數(shù)據(jù)結(jié)構(gòu),而不涉及到算法,也就是說在C中數(shù)據(jù)結(jié)構(gòu)和算法是分離的,而到C++中一類或者一個(gè)結(jié)構(gòu)體可以包含函數(shù)(這個(gè)函數(shù)在C++我們通常中稱為成員函數(shù)),C++中的結(jié)構(gòu)體和類體現(xiàn)了數(shù)據(jù)結(jié)構(gòu)和算法的結(jié)合。
擴(kuò)展:C++結(jié)構(gòu)體相關(guān)知識(shí)
C++結(jié)構(gòu)體提供了比C結(jié)構(gòu)體更多的功能,如默認(rèn)構(gòu)造函數(shù),復(fù)制構(gòu)造函數(shù),運(yùn)算符重載,這些功能使得結(jié)構(gòu)體對(duì)象能夠方便的傳值。
#include <iostream>
#include <vector>
using namespace std;
struct ST
{
int a;
int b;
ST() //默認(rèn)構(gòu)造函數(shù)
{
a = 0;
b = 0;
}
void set(ST* s1,ST* s2)//賦值函數(shù)
{
s1->a = s2->a;
s1->b = s2->b;
}
ST& operator=(const ST& s)//重載運(yùn)算符
{
set(this,(ST*)&s);
}
ST(const ST& s)//復(fù)制構(gòu)造函數(shù)
{
*this = s;
}
};
int main()
{
ST a ; //調(diào)用默認(rèn)構(gòu)造函數(shù)
vector<ST> v;
v.push_back(a); //調(diào)用復(fù)制構(gòu)造函數(shù)
ST s = v.at(0); //調(diào)用=函數(shù)
cout << s.a <<" " << s.b << endl;
cin >> a.a;
return 0;
}
_Pragma 操作符
在C/C++標(biāo)準(zhǔn)中,#pragma是一條預(yù)處理的指令。簡(jiǎn)單地說,#pragma是用來向編譯器傳達(dá)語(yǔ)言標(biāo)準(zhǔn)以外的一些信息。
#pragma once 指示編譯器,該頭文件應(yīng)該只被編譯一次。這與使用如下代碼來定義頭文件所達(dá)到的效果是一樣的。
#ifndef THIS_HEADER
#define THIS_HEADER
//一些頭文件的定義
#endif
在C++11中,標(biāo)準(zhǔn)定義了與預(yù)處理指令#pragma 功能相同的操作符_Pragma, 因?yàn)槭遣僮鞣?,所以,可以用于宏? _Pragma操作符的格式如下所示:
_Pragma(字符串字面量)
//例如: _Pragma("once");
變長(zhǎng)參數(shù)的宏定義以及VA_ARGS
變長(zhǎng)參數(shù)的宏定義是指在宏定義中參數(shù)列表的最后一個(gè)參數(shù)為省略號(hào),而預(yù)定義宏VA_ARGS則可以在宏定義的實(shí)現(xiàn)部分替換省略號(hào)所代表的字符串:
#include <stdio.h>
#define LOG(...){\
fprintf(stderr,"%s:Line%d:\t",__FILE__,__LINE__);\
fprintf(stderr,__VA_ARGS__);\
fprintf(stderr,"\n");\
}
int main(){
int x=3;
LOG("X=%d",x);//main.cpp:Line9: X=3
}
//編譯選項(xiàng):g++ -std=c++11 2-1-1.cpp
關(guān)于 stdout、stderr
stderr -- 標(biāo)準(zhǔn)錯(cuò)誤輸出設(shè)備
stdout -- 標(biāo)準(zhǔn)輸出設(shè)備 (printf("..")) 同 stdout。
如果輸入到文件中,stderr 是不顯示的。只有stdout和print才會(huì)顯示。上面代碼將stderr 改為 stdout 也是可以的,一樣能輸出。
程序員可以根據(jù)stderr產(chǎn)生的日志追溯到代碼中產(chǎn)生這些記錄的位置。引入這樣的特性,對(duì)于輕量級(jí)調(diào)試,簡(jiǎn)單的錯(cuò)誤輸出都是具有積極意義的。
在之前的C++標(biāo)準(zhǔn)中,將窄字符串(char)轉(zhuǎn)換成寬字符串(wchar_t)是未定義的行為。而在C++11標(biāo)準(zhǔn)中,在將窄字符串和寬字符串進(jìn)行連接時(shí),支持C++11標(biāo)準(zhǔn)的編譯器會(huì)將窄字符串轉(zhuǎn)換為寬字符串,然后再與寬字符串進(jìn)行連接
long long 整型
C++11整型的最大改變就是多了 long long。分為兩種:long long 和unsigned long long。在C++11中,標(biāo)準(zhǔn)要求long long 整型可以在不同平臺(tái)上有不同的長(zhǎng)度,但至少有64位。我們?cè)趯懗?shù)字面量時(shí),可以使用LL后綴(或是ll)標(biāo)識(shí)一個(gè)long long 類型的字面量,而ULL (或ull、Ull、uLL) 表示一個(gè)unsigned long long 類型的字面量。比如:
long long int lli=-900000000000000LL; // 有符號(hào)的long long 變量lli
unsigned long long int ulli=-900000000000ULL; // 無(wú)符號(hào)的 unsigned long long 變量ulli。
對(duì)于有符號(hào)的,下面的類型是等價(jià)的:long long、signed long long、long long int、signed long long int; 而unsigned long long 和 unsigned long long int 也是等價(jià)的。要了解平臺(tái)上long long大小的方法就是查看<climits>(或<limits.h>中的宏)。與 long long 整型相關(guān)的一共有3個(gè):LLONG_MIN、LLONG_MAX 和ULLONG_MAX, 它們分別代表了平臺(tái)上最小的long long 值、最大的long long 值,以及最大的unsigned long long 值。
#include <climits>
#include <cstdio>>
using namespace std;
//#define LOG(...){\
//fprintf(stderr,"%s:Line%d:\t",__FILE__,__LINE__);\
//fprintf(stderr,__VA_ARGS__);\
//fprintf(stderr,"\n");\
//}
int main(){
long long ll_min=LLONG_MIN;
long long ll_max=LLONG_MAX;
unsigned long long ull_max=ULLONG_MAX;
printf("min of long long:%lld\n",ll_min);//min of long long:-9223372036854775808
printf("max of long long:%lld\n",ll_max);//max of long long:9223372036854775807
printf("max of unsigned long long:%llu\n",ull_max);//min of unsigned long long:18446744073709551615
// LOG("X=%d",x);//main.cpp:Line9: X=3
}
//編譯選項(xiàng):g++ -std=c++11 2-1-1.cpp
18446744073709551615 用16進(jìn)制表示是0xFFFFFFFFFFFFFFFF(16個(gè)F),所以,在我們的實(shí)驗(yàn)機(jī)上,long long 是一個(gè)64位的類型。
擴(kuò)展的整型
有些整型的名字如:UINT、__int16、u64、int64_t, 等等。這些類型有的源自編譯器的自行擴(kuò)展,有的則來自某些編程環(huán)境(比如工作在Linux內(nèi)核代碼中)。事實(shí)上,在C++11中一共只定義了以下5種標(biāo)準(zhǔn)的有符號(hào)整型:
- signed char
- short int
- int
- long int
- long long int
標(biāo)準(zhǔn)同時(shí)規(guī)定,每一種有符號(hào)整型都有一種對(duì)應(yīng)的無(wú)符號(hào)整數(shù)版本,且有符號(hào)整型與其對(duì)應(yīng)的無(wú)符號(hào)整型具有相同的存儲(chǔ)空間大小。
在實(shí)際的編程中,由于這5種基本的整型適用性有限,所以有時(shí)編譯器出于需要,也會(huì)自行擴(kuò)展一些整型。在C++11中,標(biāo)準(zhǔn)對(duì)這樣的擴(kuò)展做出了一些規(guī)定。具體地講,除了標(biāo)準(zhǔn)整型之外,C++11標(biāo)準(zhǔn)允許編譯器擴(kuò)展自有的所謂擴(kuò)展整型。這些擴(kuò)展整型的長(zhǎng)度(占用內(nèi)存的位數(shù))可以比最長(zhǎng)的標(biāo)準(zhǔn)整型(long long int, 通常是一個(gè)64位長(zhǎng)度的數(shù)據(jù))還長(zhǎng),也可以介于兩個(gè)標(biāo)準(zhǔn)整數(shù)的位數(shù)之間。
C++11規(guī)定,擴(kuò)展的整型必須和標(biāo)準(zhǔn)類型一樣,有符號(hào)類型和無(wú)符號(hào)類型占用同樣大小的內(nèi)存空間。而由于C/C++是一種弱類型語(yǔ)言,當(dāng)運(yùn)算、傳參等類型不匹配的時(shí)候,整型間會(huì)發(fā)生隱式的轉(zhuǎn)換,這種過程通常被稱為整型的提升,比如:
int(a) + (long long)b
通常就會(huì)導(dǎo)致變量(int)a被提升為long long類型后才與(long long)b 進(jìn)行運(yùn)算。而無(wú)論是擴(kuò)展的整型還是標(biāo)準(zhǔn)的整型,其轉(zhuǎn)化的規(guī)則會(huì)由它們的"等級(jí)"決定。通常情況下:有如下原則:
- 長(zhǎng)度越大的整型等級(jí)越高,比如long long int的等級(jí)會(huì)高于int。
- 長(zhǎng)度相同的情況下,標(biāo)準(zhǔn)整型的等級(jí)高于擴(kuò)展類型,比如long long int和_int64如果都是64位長(zhǎng)度,則longlong int類型的等級(jí)更高。
- 相同大小的有符號(hào)類型和無(wú)符號(hào)類型的等級(jí)相同,long long int 和unsigned long long int的等級(jí)就相同。
而在進(jìn)行隱式的整型轉(zhuǎn)換的時(shí)候,一般是按照低等級(jí)整型轉(zhuǎn)換為高等級(jí)整型,有符號(hào)的轉(zhuǎn)換為無(wú)符號(hào)。這種規(guī)則跟C++98的整型轉(zhuǎn)換規(guī)則是一致的。
如果編譯器定義一些自有的整型,即使這樣自定義的整型由于名稱并沒有被標(biāo)準(zhǔn)收入,因而可移植性并不能得到保證,但至少編譯器開發(fā)者和程序員不用擔(dān)心自定義的擴(kuò)展整型與標(biāo)準(zhǔn)整型間在使用規(guī)則上(尤其是整型提升)存在不同的認(rèn)識(shí)了。
宏__cplusplus
在C和C++混合編寫的代碼中可以看到:
#ifdef__cplusplus
extern "C"{
#endif
//一些代碼
#ifdef__cplusplus
}
#endif
這種類型的頭文件可以被#include到C文件中進(jìn)行編譯,也可以被#include到C++文件中進(jìn)行編譯。由于extern "C"可以抑制C++對(duì)函數(shù)名、變量名等符號(hào)(symbol)進(jìn)行名稱重整(name mangling), 因此編譯出的C目標(biāo)文件和C++目標(biāo)文件中的變量、函數(shù)名稱等符號(hào)都是相同的(否則不相同),鏈接器可以可靠地對(duì)兩種類型的目標(biāo)文件進(jìn)行連接。這樣該做法成為了C與C++混用頭文件的典型做法。
事實(shí)上,__cplusplus這個(gè)宏通常被定義為一個(gè)整型值。而隨著標(biāo)準(zhǔn)變化,__cplusplus宏一般會(huì)是一個(gè)比以往標(biāo)準(zhǔn)中更大的值。在C++11標(biāo)準(zhǔn)中,宏__cplusplus被預(yù)定義為201103L。比如程序員在想確定代碼是使用支持C++11編譯器進(jìn)行編譯時(shí),那么可以按下面的方法進(jìn)行檢測(cè):
#include <iostream>
using namespace std;
int main(int argc,char** argv)
{
#if __cplusplus<201103L
#error"should use C++11 implementation"
#endif
}
預(yù)處理指令#error,使得不支持C++11的代碼編譯立即報(bào)錯(cuò)并終止編譯。
extern "C"用法解析
extern "C"的主要作用就是為了能夠正確實(shí)現(xiàn)C++代碼調(diào)用其他C語(yǔ)言代碼。加上 extern "C"后,會(huì)指示編譯器這部分代碼按C語(yǔ)言的進(jìn)行編譯,而不是C++的。由于C++支持函數(shù)重載,因此編譯器編譯函數(shù)的過程中會(huì)將函數(shù)的參數(shù)類型也加到編譯后的代碼中,而不僅僅是函數(shù)名;而C語(yǔ)言并不支持函數(shù)重載,因此編譯C語(yǔ)言代碼的函數(shù)時(shí)不會(huì)帶上函數(shù)的參數(shù)類型,一般之包括函數(shù)名。
標(biāo)準(zhǔn)頭文件
#ifndef __INCvxWorksh /*防止該頭文件被重復(fù)引用*/
#define __INCvxWorksh
#ifdef __cplusplus //__cplusplus是cpp中自定義的一個(gè)宏
extern "C" { //告訴編譯器,這部分代碼按C語(yǔ)言的格式進(jìn)行編譯,而不是C++的
#endif
/**** some declaration or so *****/
#ifdef __cplusplus
}
\#endif
\#endif /* __INCvxWorksh */
1、extern關(guān)鍵字
extern是C/C++語(yǔ)言中表明函數(shù)和全局變量作用范圍(可見性)的關(guān)鍵字,該關(guān)鍵字告訴編譯器,其聲明的函數(shù)和變量可以在本模塊或其它模塊中使用。
通常,在模塊的頭文件中對(duì)本模塊提供給其它模塊引用的函數(shù)和全局變量以關(guān)鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數(shù)時(shí)只需包含模塊A的頭文件即可。這樣,模塊B中調(diào)用模塊A中的函數(shù)時(shí),在編譯階段,模塊B雖然找不到該函數(shù),但是并不會(huì)報(bào)錯(cuò);它會(huì)在鏈接階段中從模塊A編譯生成的目標(biāo)代碼中找到此函數(shù)。
與extern對(duì)應(yīng)的關(guān)鍵字是static,被它修飾的全局變量和函數(shù)只能在本模塊中使用。因此,一個(gè)函數(shù)或變量只可能被本模塊使用時(shí),其不可能被extern “C”修飾。
2、被extern "C"修飾的變量和函數(shù)是按照C語(yǔ)言方式編譯和鏈接的
首先看看C++中對(duì)類似C的函數(shù)是怎樣編譯的。
作為一種面向?qū)ο蟮恼Z(yǔ)言,C++支持函數(shù)重載,而過程式語(yǔ)言C則不支持。函數(shù)被C++編譯后在符號(hào)庫(kù)中的名字與C語(yǔ)言的不同。例如,假設(shè)某個(gè)函數(shù)的原型為:
void foo( int x, int y );
該函數(shù)被C編譯器編譯后在符號(hào)庫(kù)中的名字為_foo,而C++編譯器則會(huì)產(chǎn)生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機(jī)制,生成的新名字稱為“mangled name”)。
_foo_int_int這樣的名字包含了函數(shù)名、函數(shù)參數(shù)數(shù)量及類型信息,C++就是靠這種機(jī)制來實(shí)現(xiàn)函數(shù)重載的。 例如,在C++中,函數(shù)void foo( int x, int y )與void foo( int x, float y )編譯生成的符號(hào)是不相同的,后者為_foo_int_float。
同樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以"."來區(qū)分。而本質(zhì)上,編譯器在進(jìn)行編譯時(shí),與函數(shù)的處理相似,也為類中的變量取了一個(gè)獨(dú)一無(wú)二的名字,這個(gè)名字與用戶程序中同名的全局變量名字不同。
extern “C”這個(gè)聲明的真實(shí)目的是為了 實(shí)現(xiàn)C++與C及其它語(yǔ)言的混合編程。
應(yīng)用場(chǎng)合:
- C++代碼調(diào)用C語(yǔ)言代碼,在C++的頭文件中使用 (而在C語(yǔ)言的頭文件中,對(duì)其外部函數(shù)只能指定為extern類型,C語(yǔ)言中不支持extern "C"聲明,在.c文件中包含了extern "C"時(shí)會(huì)出現(xiàn)編譯語(yǔ)法錯(cuò)誤。)
/* c語(yǔ)言頭文件:cExample.h */
\#ifndef C_EXAMPLE_H
\#define C_EXAMPLE_H
extern int add(int x,int y); //注:寫成extern "C" int add(int , int ); 也可以
\#endif
/* c語(yǔ)言實(shí)現(xiàn)文件:cExample.c */
\#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++實(shí)現(xiàn)文件,調(diào)用add:cppFile.cpp
extern "C"
{
#include "cExample.h" //注:此處不妥,如果這樣編譯通不過,換成 extern "C" int add(int , int ); 可以通過
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++調(diào)用一個(gè)C語(yǔ)言編寫的.DLL時(shí),當(dāng)包括.DLL的頭文件或聲明接口函數(shù)時(shí),應(yīng)加extern "C"{}。
- 在C中引用C++語(yǔ)言中的函數(shù)和變量時(shí),C++的頭文件需添加extern "C",但是在C語(yǔ)言中不能直接引用聲明了extern "C"的該頭文件,應(yīng)該僅將C文件中將C++中定義的extern "C"函數(shù)聲明為extern類型
//C++頭文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++實(shí)現(xiàn)文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C實(shí)現(xiàn)文件 cFile.c
/* 這樣會(huì)編譯出錯(cuò):#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
靜態(tài)斷言
斷言:運(yùn)行時(shí)與預(yù)處理時(shí)
斷言就是將一個(gè)返回值總是需要為真的判別式放在語(yǔ)句中,用于排除在設(shè)計(jì)的邏輯上不應(yīng)該產(chǎn)生的情況。比如一個(gè)函數(shù)總需要輸入在一定的范圍內(nèi)的參數(shù),那么就可以對(duì)該參數(shù)使用斷言,以迫使在該參數(shù)發(fā)生異常的時(shí)候程序退出,從而避免程序陷入邏輯的混亂。
在C++中,標(biāo)準(zhǔn)在<cassert>或<assert.h>頭文件中為程序員提供了assert宏,用于在運(yùn)行時(shí)進(jìn)行斷言。
(宏:C++ 宏定義將一個(gè)標(biāo)識(shí)符定義為一個(gè)字符串,源程序中的該標(biāo)識(shí)符均以指定的字符串來代替。前面已經(jīng)說過,預(yù)處理命令不同于一般C++語(yǔ)句。因此預(yù)處理命令后通常不加分號(hào)。這并不是說所有的預(yù)處理命令后都不能有分號(hào)出現(xiàn)。由于宏定義只是用宏名對(duì)一個(gè)字符串進(jìn)行簡(jiǎn)單的替換,因此如果在宏定義命令后加了分號(hào),將會(huì)連同分號(hào)一起進(jìn)行置換。)
#include <cassert>
using namespace std;
//一個(gè)簡(jiǎn)單的堆內(nèi)存數(shù)組分配函數(shù)(問題,如何區(qū)分是在堆還是在棧進(jìn)行內(nèi)存分配)
char* ArrayAlloc(int n){
assert(n>0); //斷言,n必須大于0
return new char[n];
}
int main(){
char* a=ArrayAlloc(0);
}
接著,可以定義宏NDEBUG來禁用assert宏。這對(duì)發(fā)布程序來說還是必要的。assert宏在<cassert>中的實(shí)現(xiàn)方式類似于下列形式:
#ifdef NDEBUG
#define assert(expr)(static_cast<void>(0))
#else
...
#endif
一旦定義了NDEBUG宏,assert宏將被展開為一條無(wú)意義的C語(yǔ)句(通常會(huì)被編譯器優(yōu)化掉)。
通過預(yù)處理指令#if和#error的配合,也可以讓程序員在預(yù)處理階段進(jìn)行斷言。比如GNU的cmathcalls.h頭文件中,我們會(huì)看到如下代碼:
#ifndef _COMPLEX_H
#error "Never use<bits/cmathcalls.h>direcctly;include<complex.h>instead."
#endif
如果程序員直接包含頭文件<bits/cmathcalls.h>并進(jìn)行編譯,就會(huì)引發(fā)錯(cuò)誤。#error指令會(huì)將后面的語(yǔ)句輸出,從而提醒用戶不要直接使用這個(gè)頭文件,而應(yīng)該包含頭文件<complex.h>.這樣一來,通過預(yù)處理時(shí)的斷言,庫(kù)發(fā)布者就可以避免一些頭文件的引用問題。
(斷言assert宏只有在程序運(yùn)行時(shí)才能起作用。而#error只在編譯器預(yù)處理時(shí)才能起作用。)有的時(shí)候,我們希望在編譯時(shí)能做一些斷言。
#include<cassert>
using namespace std;
//枚舉編譯器對(duì)各種特性的支持,每個(gè)枚舉值占一位
enum FeatureSupports{
C99=0x0001,
ExtInt=0x0002,
SAssert=0x0004,
NoExcept=0x0008,
SMAX=0X0010,
};
//一個(gè)編譯器類型,包括名稱、特性支持等
struct Compiler{
const char*name;
int spp;//使用FeatureSupports枚舉
};
int main(){
//檢查枚舉值是否完備
assert((SMAX-1)==(C99|ExtInt|SAssert|NoExcept));
Compiler a={"abc",(C99|SAssert)};
//...
if(a.spp&C99) {
//一些代碼...
}
}
//編譯選項(xiàng):g++2-5-2.cpp
我們編寫了一個(gè)枚舉類型FeatureSupports,用于列舉編譯器對(duì)各種特性的支持。而結(jié)構(gòu)體Compiler則包含了一個(gè)int類型spp。由于各種特性都具有"支持"和"不支持"兩種狀態(tài),所以,為了節(jié)省空間,我們讓每個(gè)FeatureSupport的枚舉值占據(jù)一個(gè)特定的比特位置,并在使用時(shí)通過"或"運(yùn)算壓縮地存儲(chǔ)在Compiler的spp成員中(即bitset的概念)。在使用時(shí)則可以通過檢查spp的某位來判斷編譯器對(duì)特性是否支持。
這樣的枚舉值非常多,而且還會(huì)在代碼維護(hù)中不斷增加。那么代碼編寫者必須想出辦法來對(duì)這些枚舉進(jìn)行校驗(yàn),比如查驗(yàn)是否有重位等。(在本例中程序員的做法是使用一個(gè)"最大枚舉" SMAX,并通過比較SMAX-1與其他枚舉的或運(yùn)算值來驗(yàn)證是否有枚舉值重位)。
assert是一個(gè)運(yùn)行時(shí)的斷言,意味著不運(yùn)行程序我們將無(wú)法得知是否有枚舉重位。在一些情況下,這是不可接受的,因?yàn)榭赡軉未芜\(yùn)行代碼并不會(huì)調(diào)用到assert相關(guān)的代碼路徑。因此這樣的校驗(yàn)最好是在編譯時(shí)期就能完成。
#include<cassert>
#include<cstring>
using namespace std;
template<typename T,typename U>int bit_copy(T&a,U&b){
assert(sizeof(b)==sizeof(a));
memcpy(&a,&b,sizeof(b));
};
int main(){
int a=0x2468;
double b;
bit_copy(a,b);
}
assert是要保證a和b兩種類型的長(zhǎng)度一致,這樣bit_copy才能夠保證復(fù)制操作不會(huì)遇到越界等問題。我們還是使用assert的這樣的運(yùn)行時(shí)斷言,但如果bit_copy不被調(diào)用,我們將無(wú)法觸發(fā)該斷言。實(shí)際上,正確產(chǎn)生斷言的時(shí)機(jī)應(yīng)該是模板實(shí)例化時(shí),即編譯時(shí)期。
他們的解決方法就是進(jìn)行編譯時(shí)期的斷言,即所謂的"靜態(tài)斷言"。事實(shí)上,利用語(yǔ)言規(guī)則實(shí)現(xiàn)靜態(tài)斷言的討論非常多,比如典型的實(shí)現(xiàn)是開源庫(kù)Boost內(nèi)置的BOOST_STATIC_ASSERT斷言機(jī)制(利用sizeof操作符)。我們可以利用"除0"會(huì)導(dǎo)致編譯器報(bào)錯(cuò)這個(gè)特性來實(shí)現(xiàn)靜態(tài)斷言。
#define assert_static(e)\
do{\
enum{assert_static__=1/(e);\
}while(0)
#include<cstring>
using namespace std;
#define assert_static(e)\
do{\
enum{assert_static__=1/(e)};\
}while(0)
template<typename T,typename U>int bit_copy(T&a,U&b){
assert_static(sizeof(b)==sizeof(a));
memcpy(&a,&b,sizeof(b));
};
int main(){
int a=0x2468;
double b;
bit_copy(a,b);
}
無(wú)論哪種方式的靜態(tài)斷言,其缺陷都是很明顯的:診斷信息不夠充分,不熟悉該靜態(tài)斷言實(shí)現(xiàn)的時(shí)候,可能一時(shí)無(wú)法將錯(cuò)誤對(duì)應(yīng)到斷言錯(cuò)誤上,從而難以準(zhǔn)確定位錯(cuò)誤的根源。
在C++11標(biāo)準(zhǔn)中,引入了static_assert斷言來解決這個(gè)問題。static_assert使用起來非常簡(jiǎn)單,接收兩個(gè)參數(shù),一個(gè)是斷言表達(dá)式,這個(gè)表達(dá)式通常需要返回一個(gè)bool值;一個(gè)則是警告信息,通過也就是一段字符串。我們可以用static_assert進(jìn)行替換。
template<typename t,typename u>int bit_copy(t&a,u&b){
static_assert(sizeof(b)==sizeof(a),"the parameters of bit_copy must have same width.");
memcpy(&a,&b,sizeof(b));
};
總代碼:
#include<cstring>
using namespace std;
template<typename t,typename u>int bit_copy(t&a,u&b){
static_assert(sizeof(b)==sizeof(a),"the parameters of bit_copy must have same width.");
memcpy(&a,&b,sizeof(b));
};
int main(){
int a=0x2468;
double b;
bit_copy(a,b);
}
[Error] static assertion failed: the parameters of bit_copy must have same width.
這種錯(cuò)誤非常清楚,也有利于程序員排錯(cuò)。而由于static_assert是編譯時(shí)候時(shí)期的斷言,其使用范圍不像assert一樣受到限制。在通常情況下,static_assert可以用于任何名字空間。
static_assert(sizeof(int)==8,"This 64-bit machine should follow this!");
int main(){
return 0;
}
#include<cstring>
using namespace std;
#define assert_static(e)\
do{\
enum{assert_static__=1/(e)};\
}while(0)
//template<typename t,typename u>int bit_copy(t&a,u&b){
// static_assert(sizeof(b)==sizeof(a),"the parameters of bit_copy must have same width.");
// memcpy(&a,&b,sizeof(b));
//};
static_assert(sizeof(int)==8,"This 64-bit machine should follow this!");
int main(){
return 0;
}
將static_assert寫在函數(shù)體外通常是較好的選擇,讓代碼閱讀者比較容易發(fā)現(xiàn)static_assert為斷言而非用戶定義的函數(shù)。反過來講,必須注意的是,static_assert的斷言表達(dá)式的結(jié)果必須是在編譯時(shí)期可以計(jì)算的表達(dá)式,即必須是常量表達(dá)式。
而如果有變量存在,且只需要運(yùn)行時(shí)的檢查,那么還是應(yīng)該使用assert宏。
noexcept修飾符與noexcept操作符
相比于斷言適應(yīng)于排除邏輯上不可能存在的狀態(tài),異常通常是用于邏輯上可能發(fā)生的錯(cuò)誤。在C++98中,我們看到了一整套完整的不同于C的異常處理系統(tǒng)。
void excpt_func() throw(int,double){...}
在excpt_func函數(shù)聲明之后,我們定義了一個(gè)動(dòng)態(tài)異常聲明throw(int,douuble),該聲明指出了excpt_func可能拋出的異常的類型。但是該函數(shù)被棄用了。而表示函數(shù)不會(huì)拋出異常的動(dòng)態(tài)異常聲明throw() 也被新的noexcept異常聲明所取代。
noexcept表示其修飾的函數(shù)不會(huì)拋出異常。不過與throw()動(dòng)態(tài)異常不同的是,在C++11中如果noexcept修飾的函數(shù)拋出了異常,編譯器可以選擇直接調(diào)用std:: terminate() 函數(shù)來終止程序的運(yùn)行,比基于異常機(jī)制的throw()在效率上高一些。
void excpt_func() noexcept;
void excpt_func() noexcept(常量表達(dá)式);
常量表達(dá)式的結(jié)果會(huì)被轉(zhuǎn)換成一個(gè)Bool類型的值。該值為true,表示函數(shù)不會(huì)拋出異常,反之,則有可能拋出異常。這里不帶常量表達(dá)式的noexcept相當(dāng)于聲明了noexcept(true)
在通常情況下,在C++11中使用noexcept可以有效地阻止異常的傳播與擴(kuò)散。
#include<iostream>
using namespace std;
void Throw(){throw 1;}
void NoBlockThrow(){Throw();}
void BlockThrow() noexcept{Throw();}
int main(){
try{
Throw();
}
catch(...){
cout<<"Found throw."<<endl; //Found throw.
}
try{
NoBlockThrow();
}
catch(...){
cout<<"Throw is not blocked."<<endl;//Throw is not blocked.
}
try{
BlockThrow();//terminate called after throwing an instance of 'int'
}
catch(...){
cout<<"Found throw 1."<<endl;
}
}
結(jié)果:
![jietu2.jpg-69.7kB][7]
我們定義了Throw函數(shù),該函數(shù)的唯一作用是拋出一個(gè)異常。而NoBlockThrow是一個(gè)調(diào)用Throw的普通函數(shù),BlockThrow則是一個(gè)noexcept修飾的函數(shù)。從main的運(yùn)行中我們可以看到,NoBlockThrow會(huì)讓Throw函數(shù)拋出的異常繼續(xù)拋出,直到mian中的catch語(yǔ)句將其捕捉。而BlockThrow則會(huì)直接調(diào)用std::terminate中斷程序的執(zhí)行,從而阻止了異常的繼續(xù)傳播。
而noexcept作為一個(gè)操作符時(shí),通??梢杂糜谀0濉1热纾?/p>
template<class T>
void fun() noexcept(noexcept(T())){}
這里,fun函數(shù)是否是一個(gè)noexcept的函數(shù),將由T() 表達(dá)式是否會(huì)拋出異常所決定。這里的第二個(gè)noexcept就是一個(gè)noexcept操作符。當(dāng)其參數(shù)是一個(gè)有可能拋出異常的表達(dá)式的時(shí)候,其返回值為false,反之為true。這樣一來,我們就可以使模板函數(shù)根據(jù)條件實(shí)現(xiàn)noexcept修飾的版本或無(wú)noexcept修飾的版本。從泛型編程的角度看來,這樣的設(shè)計(jì)保證了關(guān)于"函數(shù)是否拋出異常" 這樣的問題可以通過表達(dá)式進(jìn)行推導(dǎo)。
[1]: http://blog.sina.com.cn/s/blog_6e1827e10100x0dr.html
[2]: http://static.zybuluo.com/liuchenwei/09s187tdzetd5j3t64936ugj/QQ%E6%88%AA%E5%9B%BE20151107172832.jpg
[3]: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
[4]: http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html#Function-Names
[5]: http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros
[6]: http://msdn.microsoft.com/en-us/library/b0084kay
[7]: http://static.zybuluo.com/liuchenwei/rjns85bv6hrb88wcgmou4hqf/jietu2.jpg
[8]: http://static.zybuluo.com/liuchenwei/01ist2uvxei02ykyiu6rvcka/result.jpg
[9]: http://static.zybuluo.com/liuchenwei/8sv6n9qqb9hz6rq0c5j2x3rz/p.jpg
[10]: http://static.zybuluo.com/liuchenwei/xxrcsuz09sxtd6shm8nqebix/p.jpg