一.#define
#define 是宏定義命令,宏定義就是將一個標識符定義為一個字符串,源程序中的該標識符均以指定的字符串來代替,是預編譯命令,因此會在預編譯階段被執行
1.無參宏定義
無參宏的宏名后不帶參數
其定義的一般形式為:
#define 標識符 字符串
其中的“#”表示這是一條預處理命令。凡是以“#”開頭的均為預處理命令。“define”為宏定義命令。“標識符”為所定義的宏名。“字符串”可以是常數、表達式、格式串等。
例如:
#define Success 13
這樣Success 就被簡單的定義為13
帶#的都是預處理命令, 預處理命令后通常不加分號。但並不是所有的預處理命令都不能帶分號,#define就可以,由於#define只是用宏名對一個字符串進行簡單的替換,因此如果在宏定義命令后加了分號,將會連同分號一起進行置換,如:
#define Success 13;
這樣也不會報錯,只不過Success就被定義為了 13; 既13后面跟一個分號
2.有參宏定義
C++語言允許宏帶有參數。在宏定義中的參數稱為形式參數,在宏調用中的參數稱為實際參數。
對帶參數的宏,在調用中,不僅要宏展開,而且要用實參去代換形參。
帶參宏定義的一般形式為:
#define 宏名(形參表) 字符串
在字符串中含有各個形參。在使用時調用帶參宏調用的一般形式為:宏名(實參表); 如:
#define add(x,y) (x+y) //此處要打括號,不然執行2*add(x,y) 會變成 2*x + y
int main()
{
std::cout << add(9,12) << std::endl;//輸出21
return 0;
}
這個“函數”定義了加法,但是該“函數”沒有類型檢查,有點類似模板,但沒有模板安全,可以看做一個簡單的模板。
#define也可以定義一些簡單的函數,但因為只是簡單的替換,有時后會發生一些錯誤,謹慎(不建議)使用,在替換列表中的各個參數最好使用圓括號括起來
4.宏定義中的條件編譯
在大規模的開發過程中,頭文件很容易發生嵌套包含,而#ifndef 配合 #define ,#endif 可以避免這個問題
#ifndef DATATYPE_H
#define DATATYPE_H
int a = 0;
#endif
這里的#ifndef #define #endif 其實和#pragma once 作用類似,都是為了防止多次編譯一個頭文件
5.跨平台
在大規模的開發過程中,特別是跨平台和系統的軟件里,需要用到#define
#ifdef WINDOWS
......
(#else)
......
#endif
#ifdef LINUX
......
(#else)
......
#endif
可以在編譯的時候通過#define設置編譯環境
6.宏定義中的特殊操作符
define 中的特殊操作符有#,## 和 … and __VA_ARGS__
簡單來說#,##都是類似於變量替換的功能,這里不贅述
__VA_ARGS__ 是一個可變參數的宏,這個可變參數的宏是新的C99規范中新增的,目前似乎只有gcc支持
實現思想就是宏定義中參數列表的最后一個參數為省略號(也就是三個點)。這樣預定義宏__VA_ARGS__就可以被用在替換部分中,替換省略號所代表的字符串,如:
#define PR(...) printf(__VA_ARGS__)
int main()
{
int wt=1,sp=2;
PR("hello\n"); //輸出:hello
PR("weight = %d, shipping = %d",wt,sp); //輸出:weight = 1, shipping = 2
return 0;
}
這里再介紹幾個系統的宏:
__FILE__ 宏在預編譯時會替換成當前的源文件cpp名
__LINE__宏在預編譯時會替換成當前的行號
__FUNCTION__宏在預編譯時會替換成當前的函數名稱
__DATE__:進行預處理的日期(“Mmm dd yyyy”形式的字符串文字)
__TIME__:源文件編譯時間,格式微“hh:mm:ss”
這些是在編譯器里定義的,所以F12並不能轉到定義。在打印日志的時候特別好用。如:

二.typedef
C 語言提供了 typedef 關鍵字,為現有類型創建一個新的名字。比如人們常常使用 typedef 來編寫更美觀和可讀的代碼。所謂美觀,意指 typedef 能隱藏笨拙的語法構造以及平台相關的數據類型,從而增強可移植性和以及未來的可維護性。
1.最簡單的類型替換
下面的實例為單字節數字定義了一個新類型UBYTE:
typedef unsigned char UBYTE;
在這個類型定義之后,標識符 UBYTE 可作為類型 unsigned char 的縮寫,例如:
UBYTE b1, b2;
按照慣例,定義時會大寫字母,以便提醒用戶類型名稱是一個象征性的縮寫,但也可以使用小寫字母。
2.結構體&自定類型義替換
也可以使用 typedef 來為用戶自定義的數據類型取一個新的名字。例如,您可以對結構體使用 typedef 來定義一個新的數據類型名字,然后使用這個新的數據類型來直接定義結構變量,如下:
#include <stdio.h>
#include <string.h>
typedef struct LearningBooks
{
char title[50];
char author[50];
} Book;
int main( )
{
Book book;
strcpy( book.title, "c++");
strcpy( book.author, "kevin");
book.book_id = 0001;
printf( "書標題 : %s\n", book.title);
printf( "書作者 : %s\n", book.author);
return 0;
}
3.typedef函數指針用法
typedef經常用於替換一些復制的函數指針,說到函數指針,就必須先了解一下函數指針和指針函數。了解了之后在來講typedef
1)指針函數
指針函數是指帶指針的函數,即本質是一個函數。函數返回類型是某一類型的指針,聲明格式如下:
類型標識 *函數名(參數表)
int *getptr(int x); //聲明一個參數為int , 返回值為int * 的 指針函數
首先它是一個函數,只不過這個函數的返回值是一個指針。函數返回值必須用同類型的指針變量來接受,也就是說,指針函數一定有函數返回值,而且,在主調函數中,函數返回值必須賦給同類型的指針變量,如下:
double *getptr(double x) //聲明同時定義一個返回值為double* 的指針函數
{
return &x;
};
int main()
{
double *p;
p = getptr(7); //返回double* 賦給p
return 0;
}
2)函數指針
函數指針是指向函數地址的指針變量,它的本質是一個指針變量,函數也是有分配內存的,所以指向函數地址的指針,即為函數指針,聲明函數指針格式如下:
類型說明符 (*變量名)(參數)
int (*funp) (int x); // 聲明一個 指向參數類型為 int ,返回類型為 int 的函數 的 指針
int *(*funp) (int x); // 聲明一個 指向參數類型為 int ,返回類型為 int* 的函數 的 指針
其實這里的func不能稱為函數名,應該叫做指針的變量名。這個特殊的指針指向一個返回整型值的函數。指針的聲明必須和它指向函數的聲明保持一致,如:
double (*funp)(double x); // 聲明一個 指向參數類型為 int ,返回類型為 int 的函數 的 指針
double *(*funp1)(double x); // 聲明一個 指向參數類型為 int ,返回類型為 int* 的函數 的 指針
int main()
{
funp = getnum; //報錯,因為類型對不上,右邊類型是double *(*)(double x),左邊是double(*)(double x)
funp1 = getnum; //正確,兩邊類型都是double *(*)(double x)
return 0;
}
看到這里應該能明白了函數指針的寫法,再剖析一遍int (*funp) (int x);這是我的理解:
首先,變量名為funp,而funp左邊有個*,代表funp是指針類型,這個*很關鍵,之前理解不准確,以為這個*是函數返回值,其實這個*是修飾funp本身的,代表funp是指針類型,至於具體是什么指針類型(是int?,char?,還是函數?)要結合整個表達式看,然后再看右邊有括號,說明是個函數,這個函數的參數類型為int,既funp是一個指向函數的指針,再看最左邊的int,那就說明func是一個指向參數類型為int,返回值為int的函數 的指針。
那么int *(*funp) (int x);就能瞬間秒懂了,funp是一個指向參數類型為int,返回值為int*的函數 的指針
再來看看網上流行的“右左法則”解析:
理解復雜聲明可用的“右左法則”:從變量名看起,先往右,再往左,碰到一個圓括號就調轉閱讀的方向;括號內分析完就跳出括號,還是按先右后左的順序,如此循環,直到整個聲明分析完。還是那個例子:
int (*func)(int *p);
首先找到變量名func,外面有一對圓括號,而且左邊是一個*號,這說明func是一個指針;然后跳出這個圓括號,先看右邊,又遇到圓括號,這說明(*func)是一個函數,所以func是一個指向這類函數的指針,即函數指針,這類函數具有int*類型的形參,返回值類型是int
再比如:
int (*func[5])(int *);
看到數組了,所以func是一個數組呢還是一個指針呢?先別着急,不妨回顧下數組的定義
int a[10] ; //聲明一個int型數組a,a有10個元素 , 這個很簡單吧,在進一步
int *a[10] ; //聲明一個int*型數組a,a有10個元素
所以這里可以看出來在 int *a[10] ; 中,a[10]是一個整體,*是修飾a[10]的,而不是修飾a的,如果修飾a,那么a就是一個指針了,但實際上a是數組,所以*是修飾a[10]的,意味着a[10]這個數組中的元素類型為指針。這是C++決定的,[]的優先級較高,把a[]當成一個整體來看,也就是說在類似數組的定義中,如:
類型 變量名[數量n]; 類型是用來表示數組元素的,而變量名也就是a本身是一個數組。
那么問題來了?怎么表達一個指向數組的指針呢?怎么越扯越遠了........
數組指針(也稱行指針)
定義格式如下:
類型 (*變量名)[數量n];
int (*p)[5];
因為()優先級高,所以(*p)代表p是一個指針,指向一個int型的一維數組,這個一維數組的長度是5,也可以說是p的步長。也就是說執行p+1時,p要跨過n個整型數據的長度。所以 int (*p)[5]; 的含義是定義一個數組指針,指向含5個元素的一維int型數組。
如要將二維數組賦給一指針,應這樣賦值:
int a[3][4]; //定義一個二維int型數組
int (*p)[4]; //定義一個數組指針,指向含4個元素的一維int數組。
p=a; //將該二維數組的首地址賦給p,也就是a[0]或&a[0][0]
p++; //該語句執行過后,也就是p=p+1;p跨過行a[0][]指向了行a[1][]
所以數組指針也稱指向一維數組的指針,亦稱行指針
好了,回到上面討論的int (*func[5])(int *);
再來對比
int *a[10] ; //a是一個數組,數組元素類型為int*型指針
int (*func[5])(int *); //func是一個數組,數組元素類型是什么? 看到*應該知道了是指針類型,所以答案應該是某某類型的指針,什么類型的指針呢?
再看完整的右左法則”解析:
func右邊是一個[]運算符,說明func是具有5個元素的數組;func的左邊有一個*,說明func的元素是指針(注意這里的*不是修飾func,而是修飾func[5]的,原因是[]運算符優先級比*高,func先跟[]結合,把func[]看成一個整體)。跳出這個括號,看右邊,又遇到圓括號,說明func數組的元素是函數類型的指針,它指向的函數具有int*類型的形參,返回值類型為int.
所以在分析復制的聲明時,優先級很關鍵()比較高,其次是[],再然后是*
終於扯完了,講完函數指針,指針函數,數組指針,指針數組了,最后回到我們的typedef上
用typedef來定義這些復雜的類型,比如上面的函數指針,格式為:
typedef 返回類型 (*新類型名) (參數表)
typedef int (*PTRFUN) (int);
typedef char(*PTRFUN)(int); //定義char(*)(int)的函數指針 的別名為PTRFUN
PTRFUN pfun; //直接用別名PTRFUN定義char(*)(int)類型的變量
char getchar(int a) //定義一個形參為int,返回值為char的函數
{
return '1';
}
int main()
{
pfun = getchar; //把函數賦給指針
(*pfun)(5); //用函數指針取函數,調用
return 0;
}
這樣能簡化代碼,隱藏難懂的聲明類型,方便閱讀
三.using
看某些項目源碼時,里面使用了很多using關鍵字。之前對using關鍵字的概念,一直停留在引入命名空間中。其實,using關鍵字還些其他的用途。
1.聲明命名空間
using namespace std;
2.給類型取別名, 在C++11中提出了通過using指定別名
例如:
using 別名 = 原先類型;
using ty= unsigned char;
以后使用ty value; 就代表 unsigned char value;
這不就跟typedef沒啥區別嗎,那么using 跟typedef有什么區別呢?哪個更好用些呢?
例如:
typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;
而用using:
using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;
從這個例子中,可能看不出有什么明顯的好處,但是從我個人角度,更傾向於using,因為更好理解,但是並沒有要強烈使用using的感覺
再來看下:
typedef void (*FP) (int, const std::string&);
這就是上面typedef 函數指針的用法,FP代表着的是一個函數指針,而指向的這個函數返回類型是void,接受參數是int, const std::string&。那么,讓我們換成using的寫法:
using FP = void (*) (int, const std::string&);
我想,即使第一次讀到這樣代碼,並且不了解using的童鞋也能很容易知道FP是一個別名,using的寫法把別名的名字強制分離到了左邊,而把別名指向的放在了右邊,中間用 = 號等起來,非常清晰
那么,若是從可讀性的理由支持using,力度也是稍微不足的。來看第二個理由,那就是舉出了一個typedef做不到,而using可以做到的例子:alias templates, 模板別名
template <typename T>
using Vec = MyVector<T, MyAlloc<T>>;
// usage
Vec<int> vec;
這一切都會非常的自然。
那么,若你使用typedef來做這一切:
template <typename T>
typedef MyVector<T, MyAlloc<T>> Vec;
// usage
Vec<int> vec;
當你使用編譯器編譯的時候,將會得到類似:error: a typedef cannot be a template的錯誤信息。。
所以我個人認為using和typedef都有各自的用處,並沒有誰好誰壞,而標准委員會他們的觀點是,在C++11中,鼓勵用using,而不用typedef的
最后,p是什么類型
void *(*(*p)[10]) (int*)
答案:
p是個指針,指向一個指針數組,數組的元素是指針,指向 返回值為void*,形參為int*的函數
更深入的理解:https://blog.csdn.net/hai008007/article/details/80651886
