簡介
C++98/03的設(shè)計(jì)目標(biāo):
一、比C語言更適合系統(tǒng)編程(且與C語言兼容)台诗。
二完箩、支持?jǐn)?shù)據(jù)抽象。
三拉队、支持面向?qū)ο缶幊獭?br>
四弊知、支持泛型編程。
(C++模板使得C++近乎成為了一種函數(shù)式編程語言粱快,而且使得C++程序員擁有了模板元編程的能力秩彤。)
相比之下叔扼,C++11的整體設(shè)計(jì)目標(biāo)如下:
一、使得C++成為更好的適用于系統(tǒng)開發(fā)及庫開發(fā)的語言漫雷。
二瓜富、使得C++成為更易于教學(xué)的語言(語法更加一致化和簡單化)。
三降盹、保證語言的穩(wěn)定性与柑,以及和C++03及C語言的兼容性。
(如果說C++11只是對C++語言做了大幅度的改進(jìn)蓄坏,那么就很有可能錯過了C++11精彩的地方价捧。就是要做到,讀完這本書之后剑辫,只需要看一眼干旧,就可以說出代碼是C++98/03的,還是C++11的)妹蔽。
我想要的椎眯,就是C++11為程序員創(chuàng)造了很多更有效、更便捷的代碼編寫方式胳岂,程序員可以用簡短的代碼來完成C++98/03中同樣的功能编整,簡單到你會說"竟然這么簡單"。比起C++98/03,C++11大大縮短了代碼編寫量乳丰,最多可以將代碼縮短30%~80%掌测。
C++11相對C++98/03有哪些顯著的增強(qiáng)呢?包括以下幾點(diǎn):
一产园、通過內(nèi)存模型汞斧、線程、原子操作等來支持本地并行編程(Native Concurrency)什燕。
二粘勒、通過統(tǒng)一初始化表達(dá)式、auto屎即、declytype庙睡、移動語義等來統(tǒng)一對泛型編程的支持。
三技俐、通過constexpr乘陪、POD(概念)等更好地支持系統(tǒng)編程。
四雕擂、通過內(nèi)聯(lián)命名空間啡邑、繼承構(gòu)造函數(shù)和右值引用等,以更好地支持庫的構(gòu)建井赌。
(C++11像是個(gè)恐怖的“編程語言范型聯(lián)盟”谣拣。利用它不僅僅可以寫出面向?qū)ο蟮拇a募寨,也可以寫出過程式編程語言代碼、泛型編程語言代碼森缠、函數(shù)式編程語言代碼拔鹰、元編程編程語言代碼,或者其他贵涵。多范型的支持使得C++11語言的“硬實(shí)力”幾乎在編程語言中“無出其右”)
程序員需要抽象出的不僅僅是對象列肢,還有一些其他的概念,比如類型宾茂、類型的類型瓷马、算法、甚至是資源的生命周期跨晴,這些實(shí)際上都是C++語言可以描述的欧聘。在C++11中,這些抽象概念常常被實(shí)現(xiàn)在庫中端盆,其使用將比在C++98/03中更加方便怀骤,更加好用。
(C++11是一種所謂的“輕量級抽象編程語言”)總的來說焕妙,靈活的靜態(tài)類型蒋伦、小的抽象概念、絕佳的時(shí)間與空間運(yùn)行性能焚鹊,以及 與硬件緊密結(jié)合工作的能力 都是C++11突出的亮點(diǎn)痕届。
WG21更專注的特性(我比較關(guān)注的)
1、更傾向于使用庫而不是擴(kuò)展語言來實(shí)現(xiàn)特性末患。
2研叫、更傾向于通用的而不是特殊的手段來實(shí)現(xiàn)特性。
3璧针、增強(qiáng)代碼執(zhí)行性能和操作硬件的能力嚷炉。
4、開發(fā)能夠改變?nèi)藗兯季S方式的特性
5陈莽、融入編程現(xiàn)實(shí)
Mayers根據(jù)C++11的使用者是類的使用者,還是庫的使用者虽抄,或者特性是廣泛使用的走搁,還是庫的增強(qiáng)的來區(qū)分各個(gè)特性。具體地迈窟,可以把特性分為以下幾種:
A 類作者需要的(class writer, 簡稱為 “類作者”)
B 庫作者需要的(library writer, 簡稱為 “庫作者”)
C 所有人需要的(everyone, 簡稱為 “所有人”)
D 部分人需要的(everyone else, 簡稱為 “部分人”)
保證穩(wěn)定性和兼容性
保持與C99兼容
(這里有些庫不是特別懂私植,我們慢慢來)
#c++11 測試代碼
#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 對C99宏的一些支持:
STDC_HOSTED: 如果編譯器的目標(biāo)系統(tǒng)環(huán)境中包含完整的標(biāo)準(zhǔn)C庫,那么這個(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ù)定義宏對于多目標(biāo)平臺代碼的編寫通常具有重大意義唤反。通過以上的宏,程序員通過使用#ifdef/#endif等預(yù)處理命令鸭津,就可使得平臺相關(guān)代碼只適合于當(dāng)前平臺的代碼上編譯彤侍,從而在同一套代碼中完成對多平臺的支持。(通過這個(gè)預(yù)處理命名逆趋,就可以讓一些頭文件中的代碼在只適合當(dāng)前平臺的情況下進(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è)特性:
- 無需提供他們的定義藕各,就可以直接使用池摧。
- 預(yù)定義宏沒有參數(shù),且不可被重定義激况。
A:標(biāo)準(zhǔn)預(yù)定義宏(Standard Predefined Macros)
標(biāo)準(zhǔn)預(yù)定義宏由相關(guān)語言標(biāo)準(zhǔn)指定作彤。因此所有該標(biāo)準(zhǔn)的編譯器都可以使用這些宏。ANSI C指定了以下預(yù)定義宏:
- _FILE_ 在源文件中插入當(dāng)前源文件名乌逐;
- _LINE_ 在源代碼中插入當(dāng)前源代碼行號竭讳;
- _DATE_ 在源文件中插入當(dāng)前的編譯日期;
- _STDC_ 當(dāng)要求程序嚴(yán)格遵循ANSI C標(biāo)準(zhǔn)時(shí)該標(biāo)識被賦值為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)
這部分的宏是由編譯器指定的棘幸,是對標(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ù)定義宏的使用比較簡單菇晃,網(wǎng)上可以找到很多介紹文章,特別是標(biāo)準(zhǔn)預(yù)定義宏蚓挤。)
個(gè)人理解磺送,就像我寫PHP一樣,會有些可以直接調(diào)用的方法灿意,這些方法基本上是PHP已經(jīng)定義過的估灿。
很多現(xiàn)實(shí)的編譯器都支持C99標(biāo)準(zhǔn)中的_func_預(yù)定義標(biāo)識符功能,其基本功能就是返回所在函數(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)定義,編譯器會隱式地在函數(shù)的定義之后定義_func_標(biāo)識符荒辕。比如上述例子中的hello函數(shù)汗销,其實(shí)際的定義等同于如下代碼:
const char*hello(){
static const char*__func__="hello";//定義了一個(gè)靜態(tài)的常量。
return __func__;
_func_ 預(yù)定義標(biāo)識符對于輕量級的調(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ù)中使用一樣圃阳。不過將fun標(biāo)識符作為函數(shù)參數(shù)的默認(rèn)值是不允許的厌衔,如下例所示:
void FuncFail(string func_name=__func__){}; //無法通過編譯
(這是由于在參數(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)建對象搀绣。但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)知識
C++結(jié)構(gòu)體提供了比C結(jié)構(gòu)體更多的功能,如默認(rèn)構(gòu)造函數(shù)心包,復(fù)制構(gòu)造函數(shù)类咧,運(yùn)算符重載,這些功能使得結(jié)構(gòu)體對象能夠方便的傳值。
#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ù)處理的指令区宇。簡單地說,#pragma是用來向編譯器傳達(dá)語言標(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");
變長參數(shù)的宏定義以及VA_ARGS
變長參數(shù)的宏定義是指在宏定義中參數(shù)列表的最后一個(gè)參數(shù)為省略號逼裆,而預(yù)定義宏VA_ARGS則可以在宏定義的實(shí)現(xiàn)部分替換省略號所代表的字符串:
#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)錯誤輸出設(shè)備
stdout -- 標(biāo)準(zhǔn)輸出設(shè)備 (printf("..")) 同 stdout赦政。
如果輸入到文件中波附,stderr 是不顯示的。只有stdout和print才會顯示昼钻。上面代碼將stderr 改為 stdout 也是可以的掸屡,一樣能輸出。
程序員可以根據(jù)stderr產(chǎn)生的日志追溯到代碼中產(chǎn)生這些記錄的位置然评。引入這樣的特性仅财,對于輕量級調(diào)試,簡單的錯誤輸出都是具有積極意義的碗淌。
在之前的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)的編譯器會將窄字符串轉(zhuǎn)換為寬字符串,然后再與寬字符串進(jìn)行連接
long long 整型
C++11整型的最大改變就是多了 long long纳像。分為兩種:long long 和unsigned long long荆烈。在C++11中,標(biāo)準(zhǔn)要求long long 整型可以在不同平臺上有不同的長度竟趾,但至少有64位憔购。我們在寫常數(shù)字面量時(shí),可以使用LL后綴(或是ll)標(biāo)識一個(gè)long long 類型的字面量岔帽,而ULL (或ull玫鸟、Ull、uLL) 表示一個(gè)unsigned long long 類型的字面量犀勒。比如:
long long int lli=-900000000000000LL; // 有符號的long long 變量lli
unsigned long long int ulli=-900000000000ULL; // 無符號的 unsigned long long 變量ulli屎飘。
對于有符號的妥曲,下面的類型是等價(jià)的:long long、signed long long钦购、long long int逾一、signed long long int; 而unsigned long long 和 unsigned long long int 也是等價(jià)的。要了解平臺上long long大小的方法就是查看<climits>(或<limits.h>中的宏)肮雨。與 long long 整型相關(guān)的一共有3個(gè):LLONG_MIN、LLONG_MAX 和ULLONG_MAX, 它們分別代表了平臺上最小的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)的有符號整型:
- signed char
- short int
- int
- long int
- long long int
標(biāo)準(zhǔn)同時(shí)規(guī)定先馆,每一種有符號整型都有一種對應(yīng)的無符號整數(shù)版本,且有符號整型與其對應(yīng)的無符號整型具有相同的存儲空間大小躺彬。
在實(shí)際的編程中煤墙,由于這5種基本的整型適用性有限,所以有時(shí)編譯器出于需要宪拥,也會自行擴(kuò)展一些整型仿野。在C++11中,標(biāo)準(zhǔn)對這樣的擴(kuò)展做出了一些規(guī)定她君。具體地講脚作,除了標(biāo)準(zhǔn)整型之外,C++11標(biāo)準(zhǔn)允許編譯器擴(kuò)展自有的所謂擴(kuò)展整型缔刹。這些擴(kuò)展整型的長度(占用內(nèi)存的位數(shù))可以比最長的標(biāo)準(zhǔn)整型(long long int, 通常是一個(gè)64位長度的數(shù)據(jù))還長鳖枕,也可以介于兩個(gè)標(biāo)準(zhǔn)整數(shù)的位數(shù)之間。
C++11規(guī)定桨螺,擴(kuò)展的整型必須和標(biāo)準(zhǔn)類型一樣宾符,有符號類型和無符號類型占用同樣大小的內(nèi)存空間。而由于C/C++是一種弱類型語言灭翔,當(dāng)運(yùn)算魏烫、傳參等類型不匹配的時(shí)候辣苏,整型間會發(fā)生隱式的轉(zhuǎn)換,這種過程通常被稱為整型的提升哄褒,比如:
int(a) + (long long)b
通常就會導(dǎo)致變量(int)a被提升為long long類型后才與(long long)b 進(jìn)行運(yùn)算稀蟋。而無論是擴(kuò)展的整型還是標(biāo)準(zhǔn)的整型,其轉(zhuǎn)化的規(guī)則會由它們的"等級"決定呐赡。通常情況下:有如下原則:
- 長度越大的整型等級越高退客,比如long long int的等級會高于int。
- 長度相同的情況下链嘀,標(biāo)準(zhǔn)整型的等級高于擴(kuò)展類型萌狂,比如long long int和_int64如果都是64位長度,則longlong int類型的等級更高怀泊。
- 相同大小的有符號類型和無符號類型的等級相同茫藏,long long int 和unsigned long long int的等級就相同柏蘑。
而在進(jìn)行隱式的整型轉(zhuǎn)換的時(shí)候沮趣,一般是按照低等級整型轉(zhuǎn)換為高等級整型,有符號的轉(zhuǎn)換為無符號夯到。這種規(guī)則跟C++98的整型轉(zhuǎn)換規(guī)則是一致的枣申。
如果編譯器定義一些自有的整型售葡,即使這樣自定義的整型由于名稱并沒有被標(biāo)準(zhǔn)收入,因而可移植性并不能得到保證忠藤,但至少編譯器開發(fā)者和程序員不用擔(dān)心自定義的擴(kuò)展整型與標(biāo)準(zhǔn)整型間在使用規(guī)則上(尤其是整型提升)存在不同的認(rèn)識了天通。
宏__cplusplus
在C和C++混合編寫的代碼中可以看到:
#ifdef__cplusplus
extern "C"{
#endif
//一些代碼
#ifdef__cplusplus
}
#endif
這種類型的頭文件可以被#include到C文件中進(jìn)行編譯,也可以被#include到C++文件中進(jìn)行編譯熄驼。由于extern "C"可以抑制C++對函數(shù)名像寒、變量名等符號(symbol)進(jìn)行名稱重整(name mangling), 因此編譯出的C目標(biāo)文件和C++目標(biāo)文件中的變量、函數(shù)名稱等符號都是相同的(否則不相同)瓜贾,鏈接器可以可靠地對兩種類型的目標(biāo)文件進(jìn)行連接诺祸。這樣該做法成為了C與C++混用頭文件的典型做法。
事實(shí)上祭芦,__cplusplus這個(gè)宏通常被定義為一個(gè)整型值筷笨。而隨著標(biāo)準(zhǔn)變化,__cplusplus宏一般會是一個(gè)比以往標(biāo)準(zhǔn)中更大的值龟劲。在C++11標(biāo)準(zhǔn)中胃夏,宏__cplusplus被預(yù)定義為201103L。比如程序員在想確定代碼是使用支持C++11編譯器進(jìn)行編譯時(shí)昌跌,那么可以按下面的方法進(jìn)行檢測:
#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)錯并終止編譯。
extern "C"用法解析
extern "C"的主要作用就是為了能夠正確實(shí)現(xiàn)C++代碼調(diào)用其他C語言代碼蚕愤。加上 extern "C"后答恶,會指示編譯器這部分代碼按C語言的進(jìn)行編譯饺蚊,而不是C++的。由于C++支持函數(shù)重載悬嗓,因此編譯器編譯函數(shù)的過程中會將函數(shù)的參數(shù)類型也加到編譯后的代碼中污呼,而不僅僅是函數(shù)名;而C語言并不支持函數(shù)重載包竹,因此編譯C語言代碼的函數(shù)時(shí)不會帶上函數(shù)的參數(shù)類型燕酷,一般之包括函數(shù)名。
標(biāo)準(zhǔn)頭文件
#ifndef __INCvxWorksh /*防止該頭文件被重復(fù)引用*/
#define __INCvxWorksh
#ifdef __cplusplus //__cplusplus是cpp中自定義的一個(gè)宏
extern "C" { //告訴編譯器周瞎,這部分代碼按C語言的格式進(jìn)行編譯苗缩,而不是C++的
#endif
/**** some declaration or so *****/
#ifdef __cplusplus
}
\#endif
\#endif /* __INCvxWorksh */
1、extern關(guān)鍵字
extern是C/C++語言中表明函數(shù)和全局變量作用范圍(可見性)的關(guān)鍵字堰氓,該關(guān)鍵字告訴編譯器,其聲明的函數(shù)和變量可以在本模塊或其它模塊中使用苹享。
通常双絮,在模塊的頭文件中對本模塊提供給其它模塊引用的函數(shù)和全局變量以關(guān)鍵字extern聲明。例如得问,如果模塊B欲引用該模塊A中定義的全局變量和函數(shù)時(shí)只需包含模塊A的頭文件即可囤攀。這樣,模塊B中調(diào)用模塊A中的函數(shù)時(shí)宫纬,在編譯階段焚挠,模塊B雖然找不到該函數(shù),但是并不會報(bào)錯漓骚;它會在鏈接階段中從模塊A編譯生成的目標(biāo)代碼中找到此函數(shù)蝌衔。
與extern對應(yīng)的關(guān)鍵字是static,被它修飾的全局變量和函數(shù)只能在本模塊中使用蝌蹂。因此噩斟,一個(gè)函數(shù)或變量只可能被本模塊使用時(shí),其不可能被extern “C”修飾孤个。
2剃允、被extern "C"修飾的變量和函數(shù)是按照C語言方式編譯和鏈接的
首先看看C++中對類似C的函數(shù)是怎樣編譯的。
作為一種面向?qū)ο蟮恼Z言齐鲤,C++支持函數(shù)重載斥废,而過程式語言C則不支持。函數(shù)被C++編譯后在符號庫中的名字與C語言的不同给郊。例如牡肉,假設(shè)某個(gè)函數(shù)的原型為:
void foo( int x, int y );
該函數(shù)被C編譯器編譯后在符號庫中的名字為_foo,而C++編譯器則會產(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 )編譯生成的符號是不相同的唧席,后者為_foo_int_float。
同樣地嘲驾,C++中的變量除支持局部變量外淌哟,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名辽故,我們以"."來區(qū)分徒仓。而本質(zhì)上,編譯器在進(jìn)行編譯時(shí)誊垢,與函數(shù)的處理相似掉弛,也為類中的變量取了一個(gè)獨(dú)一無二的名字,這個(gè)名字與用戶程序中同名的全局變量名字不同喂走。
extern “C”這個(gè)聲明的真實(shí)目的是為了 實(shí)現(xiàn)C++與C及其它語言的混合編程殃饿。
應(yīng)用場合:
- C++代碼調(diào)用C語言代碼,在C++的頭文件中使用 (而在C語言的頭文件中芋肠,對其外部函數(shù)只能指定為extern類型乎芳,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時(shí)會出現(xiàn)編譯語法錯誤帖池。)
/* c語言頭文件: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語言實(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語言編寫的.DLL時(shí)携取,當(dāng)包括.DLL的頭文件或聲明接口函數(shù)時(shí),應(yīng)加extern "C"{}帮孔。
- 在C中引用C++語言中的函數(shù)和變量時(shí)雷滋,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明了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
/* 這樣會編譯出錯:#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è)返回值總是需要為真的判別式放在語句中晤斩,用于排除在設(shè)計(jì)的邏輯上不應(yīng)該產(chǎn)生的情況。比如一個(gè)函數(shù)總需要輸入在一定的范圍內(nèi)的參數(shù)姆坚,那么就可以對該參數(shù)使用斷言澳泵,以迫使在該參數(shù)發(fā)生異常的時(shí)候程序退出,從而避免程序陷入邏輯的混亂兼呵。
在C++中兔辅,標(biāo)準(zhǔn)在<cassert>或<assert.h>頭文件中為程序員提供了assert宏腊敲,用于在運(yùn)行時(shí)進(jìn)行斷言。
(宏:C++ 宏定義將一個(gè)標(biāo)識符定義為一個(gè)字符串维苔,源程序中的該標(biāo)識符均以指定的字符串來代替碰辅。前面已經(jīng)說過,預(yù)處理命令不同于一般C++語句介时。因此預(yù)處理命令后通常不加分號没宾。這并不是說所有的預(yù)處理命令后都不能有分號出現(xiàn)。由于宏定義只是用宏名對一個(gè)字符串進(jìn)行簡單的替換沸柔,因此如果在宏定義命令后加了分號循衰,將會連同分號一起進(jìn)行置換。)
#include <cassert>
using namespace std;
//一個(gè)簡單的堆內(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宏工三。這對發(fā)布程序來說還是必要的迁酸。assert宏在<cassert>中的實(shí)現(xiàn)方式類似于下列形式:
#ifdef NDEBUG
#define assert(expr)(static_cast<void>(0))
#else
...
#endif
一旦定義了NDEBUG宏,assert宏將被展開為一條無意義的C語句(通常會被編譯器優(yōu)化掉)徒蟆。
通過預(yù)處理指令#if和#error的配合胁出,也可以讓程序員在預(yù)處理階段進(jìn)行斷言型型。比如GNU的cmathcalls.h頭文件中段审,我們會看到如下代碼:
#ifndef _COMPLEX_H
#error "Never use<bits/cmathcalls.h>direcctly;include<complex.h>instead."
#endif
如果程序員直接包含頭文件<bits/cmathcalls.h>并進(jìn)行編譯,就會引發(fā)錯誤闹蒜。#error指令會將后面的語句輸出寺枉,從而提醒用戶不要直接使用這個(gè)頭文件,而應(yīng)該包含頭文件<complex.h>.這樣一來绷落,通過預(yù)處理時(shí)的斷言姥闪,庫發(fā)布者就可以避免一些頭文件的引用問題。
(斷言assert宏只有在程序運(yùn)行時(shí)才能起作用砌烁。而#error只在編譯器預(yù)處理時(shí)才能起作用筐喳。)有的時(shí)候,我們希望在編譯時(shí)能做一些斷言函喉。
#include<cassert>
using namespace std;
//枚舉編譯器對各種特性的支持避归,每個(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梳毙,用于列舉編譯器對各種特性的支持。而結(jié)構(gòu)體Compiler則包含了一個(gè)int類型spp捐下。由于各種特性都具有"支持"和"不支持"兩種狀態(tài)账锹,所以萌业,為了節(jié)省空間,我們讓每個(gè)FeatureSupport的枚舉值占據(jù)一個(gè)特定的比特位置奸柬,并在使用時(shí)通過"或"運(yùn)算壓縮地存儲在Compiler的spp成員中(即bitset的概念)生年。在使用時(shí)則可以通過檢查spp的某位來判斷編譯器對特性是否支持。
這樣的枚舉值非常多鸟缕,而且還會在代碼維護(hù)中不斷增加晶框。那么代碼編寫者必須想出辦法來對這些枚舉進(jìn)行校驗(yàn),比如查驗(yàn)是否有重位等懂从。(在本例中程序員的做法是使用一個(gè)"最大枚舉" SMAX,并通過比較SMAX-1與其他枚舉的或運(yùn)算值來驗(yàn)證是否有枚舉值重位)授段。
assert是一個(gè)運(yùn)行時(shí)的斷言,意味著不運(yùn)行程序我們將無法得知是否有枚舉重位番甩。在一些情況下侵贵,這是不可接受的,因?yàn)榭赡軉未芜\(yùn)行代碼并不會調(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兩種類型的長度一致,這樣bit_copy才能夠保證復(fù)制操作不會遇到越界等問題宴胧。我們還是使用assert的這樣的運(yùn)行時(shí)斷言漱抓,但如果bit_copy不被調(diào)用,我們將無法觸發(fā)該斷言恕齐。實(shí)際上乞娄,正確產(chǎn)生斷言的時(shí)機(jī)應(yīng)該是模板實(shí)例化時(shí),即編譯時(shí)期显歧。
他們的解決方法就是進(jìn)行編譯時(shí)期的斷言仪或,即所謂的"靜態(tài)斷言"。事實(shí)上士骤,利用語言規(guī)則實(shí)現(xiàn)靜態(tài)斷言的討論非常多范删,比如典型的實(shí)現(xiàn)是開源庫Boost內(nèi)置的BOOST_STATIC_ASSERT斷言機(jī)制(利用sizeof操作符)。我們可以利用"除0"會導(dǎo)致編譯器報(bào)錯這個(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);
}
無論哪種方式的靜態(tài)斷言到旦,其缺陷都是很明顯的:診斷信息不夠充分,不熟悉該靜態(tài)斷言實(shí)現(xiàn)的時(shí)候巨缘,可能一時(shí)無法將錯誤對應(yīng)到斷言錯誤上添忘,從而難以準(zhǔn)確定位錯誤的根源。
在C++11標(biāo)準(zhǔn)中带猴,引入了static_assert斷言來解決這個(gè)問題昔汉。static_assert使用起來非常簡單,接收兩個(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.
這種錯誤非常清楚,也有利于程序員排錯煤辨。而由于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ā)生的錯誤笋粟。在C++98中怀挠,我們看到了一整套完整的不同于C的異常處理系統(tǒng)。
void excpt_func() throw(int,double){...}
在excpt_func函數(shù)聲明之后矗钟,我們定義了一個(gè)動態(tài)異常聲明throw(int,douuble),該聲明指出了excpt_func可能拋出的異常的類型唆香。但是該函數(shù)被棄用了嫌变。而表示函數(shù)不會拋出異常的動態(tài)異常聲明throw() 也被新的noexcept異常聲明所取代吨艇。
noexcept表示其修飾的函數(shù)不會拋出異常。不過與throw()動態(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é)果會被轉(zhuǎn)換成一個(gè)Bool類型的值。該值為true凸舵,表示函數(shù)不會拋出異常祖娘,反之,則有可能拋出異常啊奄。這里不帶常量表達(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會讓Throw函數(shù)拋出的異常繼續(xù)拋出薯鼠,直到mian中的catch語句將其捕捉。而BlockThrow則會直接調(diào)用std::terminate中斷程序的執(zhí)行械蹋,從而阻止了異常的繼續(xù)傳播出皇。
而noexcept作為一個(gè)操作符時(shí),通郴└辏可以用于模板恶迈。比如:
template<class T>
void fun() noexcept(noexcept(T())){}
這里,fun函數(shù)是否是一個(gè)noexcept的函數(shù)谱醇,將由T() 表達(dá)式是否會拋出異常所決定暇仲。這里的第二個(gè)noexcept就是一個(gè)noexcept操作符。當(dāng)其參數(shù)是一個(gè)有可能拋出異常的表達(dá)式的時(shí)候副渴,其返回值為false,反之為true奈附。這樣一來,我們就可以使模板函數(shù)根據(jù)條件實(shí)現(xiàn)noexcept修飾的版本或無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