二三、編譯器
1、One Definition Rule
1)轉化單元
我們寫好的每個源文件(.cpp,.c)將其所包含的頭文件(#include <xxx.h>)合并后,稱為一個轉化單元。
編譯器單獨的將每一個轉化單元生成為對應的對象文件(.obj),對象文件包含了轉化單元的機器碼和轉化單元的引用信息(不在轉化單元中定義的對象)。
最后鏈接器將各個轉化單元的對象文件鏈接起來,生成我們的目標程序。
比如在對象文件A中包含了定義在其它轉化單元的引用,那么就去其它轉化單元的對象文件中尋找這個引用的定義來建立鏈接,如果在所有的對象文件中都找不到這個定義,那么就會生成一個鏈接錯誤。
2)未定義行為
在編寫代碼中,C++標準未做規(guī)定的行為,稱為未定義行為,未定義行為的結果是不確定的,具體不同的編譯器下會有不同的效果,比如
c=2*a++ + ++a*6;
這里先算a++還是先算++a就是一個未定義行為,比如
int x = -25602;
x= x>>2;
x的結果在不同的編譯器下是不確定的,因為這也屬于未定義行為
3)One Definition Rule(ODR)
ODR是一系列規(guī)則,而不是一個規(guī)則,程序中定義的每個對象都應有著自己的規(guī)則;但是基本上來講任何的變量、函數、類、枚舉、模板、概念(C++20)在每個轉化單元中都只允許有一個定義;
在整個程序中,非inline的函數或變量(C++17),有且僅能有一個定義
const聲明的變量或函數只在當前的源文件中有效,可以在一個項目的不同源文件定義相同的const變量
4)名稱的鏈接屬性
程序中的變量、函數、結構等都有著自己的名字,這些名字具有不同的鏈接屬性,鏈接器就是根據這些鏈接屬性來把各個對象文件鏈接起來的。鏈接屬性分為以下三種
①內部鏈接屬性:該名稱僅僅在本轉化單元中有效,如static、const聲明的變量、函數
②外部鏈接屬性:該名稱在其它轉化單元中也有效。通過extern關鍵字可以定義外部鏈接屬性
③無鏈接屬性:該名稱僅僅能夠用于該名稱的作用域內訪問
注:static變量或函數在自己的轉化單元有著自己的內存空間,而inline定義的變量只有一個內存地址
2、#define
1)用法一
#define A B //將標識符A定義為B的別名
#define 整數 int //將整數替換為int
整數 a{};
//#define實際用法
#include <iostream>
#define _HHHH_ int a //將_HHHH_ 替換為int a
#define VERSION "V2.0"
int main()
{
_HHHH_ { 250 };
std::cout << a << std::endl;
std::cout << VERSION<<std::endl;
}
2)C++中定義常量的方式
//C++定義常量的方法
const int width{1080};
//C語言中經常通常#define來定義常量
#define width 1080
#define的方式來定義常量存在一個問題,有時候并不安全
3)#define其它寫法
#define H //定義一個標識符H ,代碼中的H將會被刪除掉
int H a 相當于 int a;
//實際場景應用
#define _in_ //沒有實際意義
#define _out_
int ave(_in_ int a,_out_ int& b)
{
return a+b
}
4)取消宏的定義
//語法
#undef H //
//應用場景
#define _H_
#undef _H_ //刪除宏_H_的定義,后面的代碼不能使用
注:執(zhí)行順序為代碼編譯的順序(從上到下),而不是函數調用的順序
5)定義復雜表達式宏
#define SUM(X,Y) X+Y //使用X+Y替換SUM(X,Y)
#define AVE(X,Y) (X+Y)/2 //使用(X+Y)/2替換AVE(X,Y)
#define BIGGER(X,Y) ((X)>(Y)?(X):(Y))
SUM(100,200);
AVE(100,200);
BIGGER(100,200);
//實際應用場景
#define RELEASE(x) delete[] x;x=nullptr
int main()
{
int* a = new int[50];
RELEASE(a);
}
6)定義復雜表達式宏
// #可以將一個標識符參數字符串化
#define SHOW(X) std::cout<<#X //通過#將X處理成了字符串
SHOW(1234fg); //相當于std::cout<<"12345fg"
// ##可以連接兩個標識符
#define T1(X,Y) void X##Y(){std::cout<<#Y;}
T1(test, 22);
3、namespace
有時候為了方便管理,把相關的函數、變量、結構體等會附加到一個命名空間中
//聲明命名空間
namespace t
{
int value;
}
//訪問這個命名空間的變量
t::value
//直接使用命名空間,不推薦
using namespace t;
vlaue=255;
2)全局命名空間
雖有具有鏈接屬性的對象,只要沒有定義命名空間,就默認定義在全局命令空間中,全局命名空間中成員的訪問不用顯示的指定,當局部名稱覆蓋了全局名稱時才需要顯示的指定全局命令空間
int a;
::a=250;
3)命名空間的擴展
//第二個htd屬于對htd命名空間的擴展,weight和heigth同屬于一個命名空間
namespace htd
{
int weigth{1980};
}
namespace htd
{
int heigth{1080};
}
4)命名空間的聲明
//htd.h
namespace std
{
extern int height; //變量聲明
void test(); //函數聲明
}
//htd.cpp
#include <iostream>
#include "htd.h"
int htd::heigth{250}; //變量定義
void htd::test() //函數定義
{
std::cout<<htd::height;
}
5)命名空間的嵌套
//htd.h
namespace htd
{
namespace hack //命名空間的嵌套
{
void hackServer();
}
}
//htd.cpp
void htd::hack::hackServer()
{
...
}
void htd::sendSms()
{
...
}
6)未命名的命名空間
? 不給命名空間指定名稱,將會聲明一個未命名的命名空間。未命名的命名空間中聲明的內容一律為內部鏈接屬性,包括extern聲明的內容,未命名的命名空間僅僅在本轉化單元中有效
//t.cpp
void THack()
{
}
//x.cpp
namespace
{
void THack()
{
}
}
int main()
{
THack();
}
//
7)命名空間的別名
namespace htd
{
void sendSms();
namespace hack
{
void hackServer();
}
}
namespace hServer=htd::hack;
hServer::hackServer();
4、預處理指令邏輯
所有#開頭的代碼都是和編譯器進行打交道
1)#ifdef
#define _HEIGHT_ 1080 //#ifdef和#endif成對出現(xiàn)
#ifdef _HIGHT_
#else
#endif
//hc.h
#ifdef _HC_ //如果定義了宏_HC_,就執(zhí)行XXXX里面的代碼。如果沒有定義,則執(zhí)行YYYYY
XXXX
#else
YYYYY
#endif
//常見用法
#ifndef _HC_ //如果沒有定義了宏_HC_
#define _HC_ //則定義了宏_HC_
#else
#endif
//實際應用場景
#ifdef UNICODE //如果使用了UNICODE字符集,則使用wchar_t定義變量
wchar_t a;
#else
char a;
#endif
//通過預處理指令進行版本控制
#define VERSION 101
#if VERSION==100 //當VERSION為100時,執(zhí)行如下邏輯,否則執(zhí)行else中的邏輯
void SendSms()
{
}
#else
void SendSms()
{
}
#endif
2)#elif
//#elif語法
#define _HEIGHT_ 2080
#if _HEIGHT_ == 1080 //針對每一個分辨率執(zhí)行不同的邏輯
...
#elif _HEIGHT_ == 720
...
#else
...
#endif
//預處理指令可以進行簡單的計算//當VERSION為100時,執(zhí)行如下邏輯,否則執(zhí)行else中的邏輯
void SendSms()
{
}
5、預定義宏
1)標準預定義標識符_fun_
編譯器支持ISO C99和ISO C++ 11,即可使用該預定義標識符。用于返回函數的名稱
__func__ //返回函數的名稱
//用法示例
#include <iostream>
int main()
{
std::cout << __func__ << std::endl; //返回函數名稱,輸出main
}
2)標準預定義宏
編譯器支持ISO C99和ISO C++17標準,即可使用以下預定義宏
宏 | 說明 |
---|---|
_DATE_ | 返回源文件的編譯日期 |
_TIME_ | 返回當前轉化單元的轉化時間(可理解為代碼修改的時間) |
_FILE_ | 返回源文件的名稱 |
_LINE_ | 返回當前的行 |
__cplusplus | 當當前單元為C++時(即.cpp文件時),__cplusplus定義為一個整數,否則不是c++文件 |
#include <iostream>
int main()
{
std::cout << __func__ << std::endl; //返回函數名稱,輸出main
std::cout << __DATE__ << std::endl;
std::cout << __TIME__ << std::endl;
std::cout << __FILE__ << std::endl;
std::cout << __LINE__ << std::endl;
std::cout << __cplusplus << std::endl;
}
3)MSVC的預定義宏(可理解為微軟的VC編譯器中,預定義的一些宏)
宏 | 說明 |
---|---|
_CHAR_UNSIGNED | 如果char類型為無符號,該宏定義為1,否則為未定義 |
_COUNTER_ | 用于計數,從0開始,每使用一次都會遞增1 |
_DEBUG | 如果設置了_DEBUG宏,代表當前為調試狀態(tài)/lDd /mDd /mTd該宏定義為1,否則為未定義 |
_FUNCTION_ | 返回函數名稱 ,但是不包含修飾名 |
_FUNCDNAME_ | 函數名稱 包含修飾名 |
_FUNCSIG_ | 包含了函數簽名的函數名 |
_WIN32 | 當編譯為32位ARM,64位ARM,X68或X64定義為1,否則未定義 |
_WIN64 | 當編譯為64位ARM或x64定義為1,否則未定義。用于區(qū)別 |
_TIMESTAMP_ | 最后一次源代碼修改的時間和日期 |
#include <iostream>
int main()
{
#ifdef _CHAR_UNSIGNED //如果char為無符號類型,可以通過該預定義宏進行檢驗
std::cout << "無符號類型";
#endif
std::cout << "有符號類型" << std::endl;
std::cout << __COUNTER__ << std::endl; //代表計數
std::cout << __COUNTER__ << std::endl;
std::cout << __COUNTER__ << std::endl;
#ifdef _DEBUG //代表調試狀態(tài)
std::cout << "調試狀態(tài)" << std::endl;
std::cout << __FUNCTION__ << std::endl; //返回函數名,不包含修飾名
std::cout << __FUNCDNAME__ << std::endl; //返回函數名,包含修飾名
std::cout << __FUNCSIG__ << std::endl; //包含函數簽名,即調用、約定等信息
std::cout << __TIMESTAMP__ << std::endl; //最后一次源代碼修改的時間和日期
#endif
#ifdef _WIN32 //用于區(qū)分Win32架構或者Win64架構
std::cout << "X86" << std::endl;
#endif // _WIN32
}
只要【項目屬性】-【C/C++】-【代碼生成】-【運行庫】,選擇了調試,則_DEBUG就會顯示
注:上述宏只在微軟的VS編輯器才可以使用,其它的編譯器無法使用
6、調試
? 我們編寫好程序以后,可能存在一些bug和錯誤,對于語法上錯誤,編譯器能夠直接給出提示,而對于邏輯上的錯誤,編譯器不能夠直接發(fā)現(xiàn),調試就是一個找錯誤和改錯誤的過程
1)調試建議:為了方便調試,在編程風格上提出如下建議
①功能能模塊化就模塊化
②使用能夠體現(xiàn)出具體意義的函數名和變量名
③使用正常的縮進和代碼塊
④良好的注釋習慣
2)利用集成調試器調調試程序
VS2019繼承了一個調試器,可以利用斷點、流程跟蹤等方式來調試自己的程序
斷點就是當程序執(zhí)行到斷點位置,程序就會停下來
3)利用其它調試器
OllyDbg
X96Dbg
WinDbg
4)利用預處理指令來輸出調試信息
#define _dbg_i //先定義一個宏
#ifdef _dbg_i //如果宏存在,則執(zhí)行下面的代碼塊
std::cout<<"調試信息";
#endif
7、assert
assert宏需要頭文件cassert
1)assert語法
//assert語法
assert(bool表達式); //如果括號內的bool表達式為false,則會調用std::abort()函數,彈出下面的對話框,
2)關閉assert文章來源:http://www.zghlxwxcb.cn/news/detail-760377.html
//關閉assert
#define NDEBUG //可以在當前轉化單元關閉assert,但是這個定義必須放在#include <cassert>之前
3)static_assert(靜態(tài)斷言)文章來源地址http://www.zghlxwxcb.cn/news/detail-760377.html
//static_assert用于編譯時檢查條件
static_assert(bool表達式,"Error information"); //先檢查表達式,若表達式為假,則輸出后面的錯誤信息。如果表達式為0,則程序是不進行編譯的,此處二點bool表達式只能用于常量
//C++17新語法
static_assert(bool表達式);
到了這里,關于二三、編譯器的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!