C++ 關鍵字 Const Define Static
作者:AceTan,轉載請標明出處!
今天來討論一下在C++中很常見的三個關鍵字 Const Define Static.
0x00: const
const 限定符:有時候我們需要定義這樣一種變量,它的值是不可改變的。這時候,我們就需要用到const這個關鍵字了。
const 關鍵字在各大考試和C++筆試中經常遇到。比如下面這一道面試題:
說出const關鍵字在下列語句中的作用。
- const int a;
- int const a;
- const int *a;
- int const *a;
- int *const a;
- const int * const a;
- const char* func1();
- int GetX() const;
看完是不是一臉懵逼?看完下面的解讀,上面的問題將迎刃而解。
首先需要知道的是const修飾符所修飾的變量值不可改變。
另外需要注意的是,在用const定義關鍵字時, const int 和 int const 這兩種寫法是等價的(沒有*),它表示所定義的整型變量的值是不可改變的。
現在我們的關鍵問題是,const修飾符到底是修飾的誰。是這個整型變量,還是指針,還是指針所指向的值。
默認狀態下,const 對象僅在文件內有效。 如果想讓它在其他文件內也有效,需要添加extern關鍵字。
引用和const
const 的引用 可以把引用綁定到const對象上,就像綁定到其他對象上一樣,我們稱之為對常量的引用(reference to const)。 與普通引用不同的是,對常量的引用不能被用作修改它所綁定的對象。
舉個栗子:
int v = 123;
const int& r1 = v; // 允許將const int& 綁定到一個普通的int對象上
const int& r2 = 123; // 正確:r2是一個 常量引用
const int& r3 = r1 * 10;// 正確:r3是一個常量引用
int& r4 = r1 * 5; // 錯誤:r4是一個普通的非常量引用
int& r5 = v; // r5綁定對象v
r5 = 1; // 正確,r5并非一個常量
r1 = 1; // 錯誤:r1是一個常量引用,值不可更改
在實際編程中,如果寫的語法有錯誤,IDE一般都是會提示的,根據提示修改就好。 但我們應該記住這些常見的特性。
指針和const
指針和const鬼混在一塊,那才是噩夢的開始。
指針是對象,而引用不是。因此,可以像其他對象類型一樣,允許指針本身定為常量。常量指針(const pointer)必須要初始化。
int a = 0;
int* const p = &a; // p是一個常量指針,指向整型數a
const double pi = 3.14159;
const double* const pPi = π // pPi是一個指向常量對象pi的常量指針
是不是看著有點頭暈,理清這些關系的最好方法是從右往左讀。
拿上面的第四個來解釋一下。離pPi最近的是const,說明pPi是一個常量對象。對象的類型是什么呢,繼續往左讀,對象的類型由聲明符的其余部分所決定的,聲明符的下一個是*, 意思是pPi是一個常量指針。該聲明語句的基本數據類型(double)確定了常量對象(pPi)指向了一個double型對象。再往左讀還是個const,說明這個double型對象也是個常量。
C++ 中有兩個術語對指針是否是常量,以及指針指向的對象是否是常量加以區分。
頂層const(top-level const) 表示指針本身是個常量。
底層const(low-level const) 表示指針所指的對象是一個常量。
這兩個問題是獨立的。
const的常見用法
- const可以用來定義常量。但它更大的魅力是它可以修飾函數的參數、返回值,甚至函數的定義體。這也是實際應用中用的比較多的。
use const whenever you need
- const修飾函數參數。const修飾參數,是防止傳入的參數被意外的修改。前面講到過,可以用引用或者指針作為參數來作為輸出。 這樣是不需要加const的。其他一些情況,如果傳入的參數不需要被改變,則需要加const修飾。基本類型一般是不需要const進行修飾的,因為它采取值專遞的方式。如果是用戶自定義類型,我們一般采用引用傳遞,像 Function(A& a),其中A是用戶自定義的類型。 Function(A a)的效率不如Function(A& a),因為函數體內將產生A類型的臨時對象用于復制參數a,而臨時對象的構造、復制、析構過程都將消耗時間。但這樣聲明有一個缺點,它會使調用者誤認為出入的引用時可修改的,解決的方法就是加const進行修飾。
如果傳入的參數不需要更改,那么加const修飾在實際編程中幾乎是必須的。
- const修飾函數的返回值。const一般用來修飾以"指針傳遞"方式的函數返回值,它表示函數的返回值(即指針)的內容是不可修改的。該返回值只能被賦給加const修飾的同類型指針。修飾以"值傳遞"方式的函數返回值是沒有意義的。
const char* GetStr();
const char* someStr = GetStr(); // 正確
char* str2 = GetStr(); // 錯誤
- const修飾成員函數。 任何不會修改數據成員的函數都應該聲明為const類型。確切的說,const是修飾this指向的對象的。
class A
{
private:
int num; // 成員變量
public:
void Func(int x); // 其實原型是兩個參數 Func(A* const this, int x)
void Func2(int y) const;// 原型是Func2(const A* const this, int y)
};
void A::Func(int x)
{
num = num * x; // 正確:this所指向的對象沒有const修飾,可以更改
}
void A::Func2(int y) const
{
num = num * y; // 錯誤:this所指向的對象被const修飾,無法更改這個對象的數據。
}
至于const為什么放在后面,想必各位看官已經猜到了。原型的第一個參數是被省略了,無法修飾,大概也只能放到函數的后面了,雖然看起來怪怪的。
0x01: define
準確的說應該是#define。 它繼承自C語言,是一個預處理指令。所謂的預處理,就是在編譯之前執行一段程序,可以部分的改變我們寫的程序。 之前我們見過的#include就是一條預處理命令,它將所包含的文件替換到所用指令的地方。#define指令是用來把一個名字定義預處理變量的。預處理變量有兩種狀態:已定義與未定義。#define是用來定義它的。與之對應的是另外兩個指令分別檢查預處理變量是否已經定義。#ifdef 當且僅當變量已定義時為真,#ifndef當且僅當變量未定義時為真。一旦檢查為真,則執行后續的操作直到遇到#endif 指令為止。
預處理變量無視C++語言中關于作用域的規則
#define 的常見用法
- 頭文件保護(header guard)。在定義一個頭文件時,建議習慣性地加上頭文件保護,沒必要太在乎你的程序到底需不需要。這是一個比較好的編程習慣。 一般預處理變量名我們用所要定義的類名的大寫來命名。示例如下:
#ifndef _FORM_RIDE_H_
#define _FORM_RIDE_H_
// 你的代碼
#endif // _FORM_RIDE_H_
加了頭文件保護,別人就不用擔心是否重復引入你的文件了。
- 改變程序執行流程。 平臺的差異性,客服端和服務器的差異性,有時候為了讓代碼具有更好的兼容性,通常定義這些宏來執行不同的邏輯代碼。這時候只需要一個總的控制文件,就能讓代碼在不同的情形下執行不同的邏輯。
#ifdef _CLIENT_
if (pEntity == NULL)
{
return false;
}
string pathname = string(pEntity->GetCore()->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_STUB_)
string pathname = string(pKernel->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_MEMBER_) || defined(_SERVER_ROOM_)
string pathname = string(pKernel->GetResourcePath()) + "ini/rule/ridestrength/ride_strength_info.xml";
#endif
- 充當函數的作用。說是充當函數,其實和函數還是有挺大差別。只是在用到的地方進行替換,使程序看起來更簡潔。下面的代碼是用來檢查循環的,可以避免死循環。預設一個循環最大值,然后調用這個宏讓它檢查。
#include <iostream>
using namespace std;
int g_nMaxCirculateCount = 100;
void SetMaxCirculateCount(int count)
{
if (g_nMaxCirculateCount != count)
{
g_nMaxCirculateCount = count;
}
}
#define LoopBeginCheck(name) \
int nCheck##name##Count = 0;
#define LoopDoCheck(name) \
nCheck##name##Count++;\
if((g_nMaxCirculateCount > 0) && (nCheck##name##Count > g_nMaxCirculateCount))\
{\
cout << "文件路徑:" << __FILE__ << endl \
<< "函數:" << __FUNCTION__<< endl \
<< "所在行:" << __LINE__ << endl \
<< "循環次數:" << nCheck##name##Count << endl; \
break;\
}
int main()
{
int n = 77 * 88; // 實際需要執行的循環次數
SetMaxCirculateCount(10); // 設置的最大循環次數
LoopBeginCheck(var);
for (int i = 1; i < n; i++)
{
LoopDoCheck(var);
cout << "循環結束" << endl;
}
return 0;
}
這里的 \ 起鏈接換行的作用,##name## 是拼接參數。 __FILE__等則是一些內置宏。
0x02: static
static定義的變量存儲在靜態數據區,在靜態數據區,內存中所有的字節默認值都是0x00。
靜態變量作用范圍在一個文件內,程序開始時分配空間,結束時釋放空間,默認初始化為0,使用時可以改變其值。與全局變量不同的是,靜態變量或靜態函數只有本文件內的代碼才能訪問它,它的名字在其它文件中不可見。
static的主要有以下三個特性:
表示代碼退出一個塊后,仍然能夠存在的局部變量。因為數據會存儲在靜態數據區。
用來表示不能被其它文件訪問的全局變量和函數。
表示屬于一個類而不是屬于此類的任何特定對象的變量和函數。這個和Java中static關鍵字的意義相同。
有了static關鍵字,變量就變的有些復雜了。是時候理清一下各類型變量的作用域范圍了。
常見的變量分為如下幾種:全局變量、靜態全局變量、靜態局部變量和局部變量。
全局變量。存儲:靜態存儲區域。 作用域:在整個工程文件內都有效。
靜態全局變量。存儲:靜態存儲區域。 作用域:在定義它的文件內有效。
靜態局部變量 存儲:靜態存儲區域。 作用域:只在定義它的函數內有效。程序僅分配一次內存,函數返回后,改變量不會消失。
局部變量 存儲:內存棧中。 作用域:只在定義它的函數內有效。程序返回后局部變量被回收。
static函數在內存中只有一份,普通函數在每個被調用中維持一份拷貝。
#include <iostream>
using namespace std;
int n = 100; // 全局變量
static int m; // 靜態全局變量
class MyClass
{
public:
void ChangeX(int x); // 普通成員函數
static void ChangeY(int y); // 靜態成員函數
int GetY(); // 獲取全局變量的值
private:
int m_x; // 普通成員變量
static int m_y; // 靜態成員變量
};
void MyClass::ChangeX(int x)
{
++m_x;
static int times = 0;
++times;
cout << "第" << times << "次調用該函數" << endl;
}
void MyClass::ChangeY(int y)
{
//m_x = y; // 報錯:靜態成員函數只能引用靜態成員變量
m_y = y;
}
int MyClass::GetY()
{
cout << "靜態成員變量y的值為:" << m_y << endl;
return m_y;
}
int MyClass::m_y = 0;//定義并初始化靜態數據成員
int main()
{
n = 88;
cout << "全局變量改為" << n << endl;
cout << "靜態全局變量的初始值為" << m << endl;
MyClass cls1; // 創建第一個類
cls1.ChangeY(111); // 改變了靜態成員的值
MyClass cls2; // 創建第一個類
cls2.ChangeX(1); // 連續調用3次
cls2.ChangeX(2);
cls2.ChangeX(3);
cls2.GetY(); // 直接輸出靜態成員變量看看
return 0;
}
輸出結果:
全局變量改為88
靜態全局變量的初始值為0
第1次調用該函數
第2次調用該函數
第3次調用該函數
靜態成員變量y的值為:111
輸出結果印證了上面所討論的內容。
0x03: 結束語
const define static在實際編程中應用的非常多,各位看官應多加理解,靈活應用。