C++ #define,typedef,using用法區別


一.#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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM