typedef自定义类型名 与 类型别名 ,及与define的区别
typedef自定义类型
- 给变量一个易记且意义明确的新名字,
- 简化一些比较复杂的类型声明。
其实就是为数据类型起一个别名。
typedef unsigned char AGE; //字符类型
AGE x; //等价于 unsigned char x;
typedef int * IPointer; //指针类型
IPointer p; //等价于 int *p;
typedef char Name[10]; //数组类型
Name name1; //char name1[10];
Typedef允许程序员为数据类型创建别名,并使用别名代替实际的类型名称。Typedef的字面意思是“类型定义”。
要声明typedef,只需使用typedef关键字,然后使用别名作为类型,再使用别名:
typedef double distance_t; // define distance_t as an alias for type double
// The following two statements are equivalent: double howFar; distance_t howFar; |
按照约定,typedef名称使用“ _t”后缀声明。这有助于指示标识符表示类型,而不是变量或函数,并且还有助于防止与其他标识符的命名冲突。
请注意,a typedef并未定义新类型。相反,它只是现有类型的别名(另一个名称)。A typedef可以在可以使用常规类型的任何地方互换使用。
即使以下内容在语义上没有意义,但它是有效的C ++:
int main() { typedef long miles_t; typedef long speed_t;
miles_t distance { 5 }; speed_t mhz { 3200 };
// The following is valid, because distance and mhz are both actually type long distance = mhz;
return 0; } |
typedefs和type别名遵循与变量相同的作用域规则。该typedef小号miles_t和speed_t仅在使用main()功能。如果将它们放在另一个函数中,main()将无法访问它们。如果将它们放置main()在全局范围之外,则所有功能都可以访问它们。
但是,typedef存在一些问题。首先,很容易忘记类型名称或类型定义是第一位的。哪个是正确的?
typedef distance_t double; // incorrect typedef double distance_t; // correct |
我不记得了
其次,typedef的语法在更复杂的类型上变得丑陋,尤其是函数指针(我们将在以后的第7.8节“函数指针”中介绍):
输入别名
为了帮助解决这些问题,typedefs引入了一种改进的语法,该语法模仿了声明变量的方式。此语法称为类型别名。
给定以下typedef:
typedef double distance_t; // define distance_t as an alias for type double |
可以将其声明为以下类型别名:
using distance_t = double; // define distance_t as an alias for type double |
两者在功能上是等效的。
请注意,尽管类型别名语法使用“ using”关键字,但这是重载的含义,与using statements与名称空间相关的内容无关。
对于更高级的类型定义情况,此类型别名语法更干净,因此应首选。
使用类型别名提高可读性
类型别名的一种用途是帮助文档和可读性。数据类型的名称,如char,int,long,double,和bool是用于描述什么好键入一个函数返回,但更多的时候,我们想知道是什么目的返回值服务。
例如,考虑以下功能:
int GradeTest(); |
我们可以看到返回值是一个整数,但是该整数是什么意思呢?字母等级?遗漏了多少个问题?学生的身份证号?错误代码?谁知道!Int没有告诉我们任何信息。
using testScore_t = int; testScore_t GradeTest(); |
但是,使用返回类型为testScore_t的函数很明显,该函数正在返回表示测试分数的类型。
使用类型别名来简化代码维护
类型别名还允许您更改对象的基础类型,而不必更改大量代码。例如,如果您使用a short持有学生的ID号,但后来又决定需要一个ID,则long必须梳理大量代码并替换short为long。可能很难弄清楚哪些shorts被用于保存ID号以及哪些被用于其他目的。
但是,使用类型别名,您要做的就是更改using studentID_t = short;为using studentID_t = long;。但是,在将类型别名的类型更改为其他类型族中的类型时(例如,将整数更改为浮点值,反之亦然),仍需小心!新类型可能有比较或整数/浮点除法问题,或其他旧类型没有的问题。
使用类型别名进行平台无关的编码
类型别名的另一个优点是,它们可以用于隐藏平台特定的细节。在某些平台上,an int为2字节,在其他平台上为4字节。因此,int在编写与平台无关的代码时,使用存储2个以上字节的信息可能具有潜在的危险。
因为char,short,int,并long没有给出其大小的指示,这是很常见的跨平台程序使用类型别名定义别名包括在比特类型的大小。例如,int8_t将是一个8位带符号整数,int16_t一个16位带符号整数和int32_t一个32位带符号整数。以这种方式使用类型别名有助于防止错误,并使对变量大小的假设变得更加清晰。
为了确保每个别名类型都能解析为正确大小的类型,通常将这种类型别名与预处理器指令结合使用:
#ifdef INT_2_BYTES using int8_t = char; using int16_t = int; using int32_t = long; #else using int8_t = char; using int16_t = short; using int32_t = int; #endif |
在整数只有2个字节的计算机上,INT_2_BYTES可以#defined定义,并且程序将使用最顶层的类型别名进行编译。在整数为4个字节的计算机上,INT_2_BYTES未定义将导致使用底部的类型别名集。以这种方式,int8_t将解析为一个1个字节的整数,int16_t将解析为一个2字节整数,并且int32_t将解析为一个4字节整数使用的组合char,short,int,和long适合于该程序正在编译上的机器。
正是这样定义了int8_tC ++ 11中引入的固定宽度整数(如)(在第4.6节中介绍了-固定宽度整数和size_t)!
这也是int8_t被视为char来源的问题- int8_t是的类型别名char,因此仅是a的别名,char而不是唯一类型。结果是:
#include <cstdint> // for fixed-width integers #include <iostream>
int main() { std::int8_t i{ 97 }; // int8_t is actually a type alias for signed char std::cout << i;
return 0; } |
该程序打印:
一个
不是97,因为std :: cout打印char为ASCII字符,而不是数字。
使用类型别名使复杂类型变得简单
尽管到目前为止我们只处理了简单的数据类型,但是在高级C ++中,您可以看到这样声明的变量和函数:
std::vector<std::pair<std::string, int> > pairlist;
bool hasDuplicates(std::vector<std::pair<std::string, int> > pairlist) { // some code here } |
打字std::vector<std::pair<std::string, int> >无处不在,你需要使用类型可以很麻烦。使用类型别名要容易得多:
using pairlist_t = std::vector<std::pair<std::string, int> >; // make pairlist_t an alias for this crazy type
pairlist_t pairlist; // instantiate a pairlist_t variable
bool hasDuplicates(pairlist_t pairlist) // use pairlist_t in a function parameter { // some code here } |
好多了!现在我们只需要键入“ pairlist_t”而不是std::vector<std::pair<std::string, int> >。
如果您不知道什么是std :: vector,std :: pair或所有这些疯狂的尖括号,请不要担心。您在这里真正需要了解的唯一一件事是,类型别名使您可以采用复杂的类型并给它们一个简单的名称,这使这些类型更易于使用和理解。
最佳实践
优先于typedef而使用类型别名,并广泛使用它们来记录类型的含义。
函数指针一般用于回调,例如信号处理,libcurl等会应用到回调。回调是比较常用的技术,而回调就要涉及函数指针。
当我们的程序中有以下函数:
void printHello(int i);
然后我们要定义一个函数指针,指向printHello,并且调用这个方法,代码如下:
void (*pFunc)(int);
pFunc = &printHello;
(*pFunc)(110);
其中void (*pFunc)(int)是声明一个函数指针,指向返回值是void,调用参数是(int)的函数,变量名是pFunc,pFunc就是函数指针了,以前是函数指针的简单用法。
大家可以看到,声明一个函数指针是比较复杂的,尤其是当你要在多处地方声明同一个类型的函数指针变量,代码更加复杂,所以有下面简化的做法:
typedef void (*PrintHelloHandle)(int);
使用代码如下:
PrintHelloHandle pFunc;
pFunc = &printHello;
(*pFunc)(110);
以后其他地方的程序需要声明类似的函数指针,只需要下面代码:
PrintHelloHandle pFuncOther;
这样,我们的代码就变得更加简洁易懂。
typedef和#define的用法与区别
一、typedef的用法
在C/C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像:
typedef int INT;
typedef int ARRAY[10];
typedef (int*) pINT;
typedef可以增强程序的可读性,以及标识符的灵活性,但它也有“非直观性”等缺点。
二、#define的用法
#define为一宏定义语句,通常用它来定义常量(包括无参量与带参量),以及用来实现那些“表面似和善、背后一长串”的宏,它本身并不在编
译过程中进行,而是在这之前(预处理过程)就已经完成了,但也因此难以发现潜在的错误及其它代码维护问题,它的实例像:
#define INT int
#define TRUE 1
#define Add(a,b) ((a)+(b));
#define Loop_10 for (int i=0; i<10; i++)
在Scott Meyer的Effective C++一书的条款1中有关于#define语句弊端的分析,以及好的替代方法,大家可参看。
三、typedef与#define的区别
从以上的概念便也能基本清楚,typedef只是为了增加可读性而为标识符另起的新名称(仅仅只是个别名),而#define原本在C中是为了定义常量
,到了C++,const、enum、inline的出现使它也渐渐成为了起别名的工具。有时很容易搞不清楚与typedef两者到底该用哪个好,如#define
INT int这样的语句,用typedef一样可以完成,用哪个好呢?我主张用typedef,因为在早期的许多C编译器中这条语句是非法的,只是现今的
编译器又做了扩充。为了尽可能地兼容,一般都遵循#define定义“可读”的常量以及一些宏语句的任务,而typedef则常用来定义关键字、冗
长的类型的别名。
宏定义只是简单的字符串代换(原地扩展),而typedef则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变
量的功能。请看上面第一大点代码的第三行:
typedef (int*) pINT;
以及下面这行:
#define pINT2 int*
实践中见差别:pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int *a, b; 表示定义了一个整型指针变量a和整型变量b。
1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。例如:
#define PI 3.1415926
程序中的:area=PI*r*r 会替换为3.1415926*r*r
如果你把#define语句中的数字9 写成字母g 预处理也照样带入。
2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。
3)typedef int * int_ptr;
与
#define int_ptr int *
作用都是用int_ptr代表 int * ,但是二者不同,正如前面所说 ,#define在预处理 时进行简单的替换,而typedef不是简单替换 ,而是采用如同定义变量的方法那样来声明一种类型。也就是说;
//refer to (xzgyb(老达摩))
#define int_ptr int *
int_ptr a, b; //相当于int * a, b; 只是简单的宏替换
typedef int* int_ptr;
int_ptr a, b; //a, b 都为指向int的指针,typedef为int* 引入了一个新的助记符
这也说明了为什么下面观点成立
//QunKangLi(维护成本与程序员的创造力的平方成正比)
typedef int * pint ;
#define PINT int *
那么:
const pint p ;//p不可更改,但p指向的内容可更改
const PINT p ;//p可更改,但是p指向的内容不可更改。
pint是一种指针类型 const pint p 就是把指针给锁住了 p不可更改
而const PINT p 是const int * p 锁的是指针p所指的对象。
typedef的四个用途和两个陷阱
用途一:
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:
char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针,
// 和一个字符变量;
以下则可行:
typedef char* PCHAR; // 一般用大写
PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针
虽然:
char *pa, *pb;
也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的地方,typedef的方式更省事。
用途二:
用在旧的C代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名 对象名,如:
struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
而在C++中,则可以直接写:结构名 对象名,即:
tagPOINT1 p1;
估计某人觉得经常多写一个struct太麻烦了,于是就发明了:
typedef struct tagPOINT
{
int x;
int y;
}POINT;
POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候
或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。
用途三:
用typedef来定义与平台无关的类型。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
typedef long double REAL;
在不支持 long double 的平台二上,改为:
typedef double REAL;
在连 double 都不支持的平台三上,改为:
typedef float REAL;
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
用途四:
为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:
1. 原声明:int *(*a[5])(int, char*);
变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int *(*pFun)(int, char*);
原声明的最简化版:
pFun a[5];
2. 原声明:void (*b[10]) (void (*)());
变量名为b,先替换右边部分括号里的,pFunParam为别名一:
typedef void (*pFunParam)();
再替换左边的变量b,pFunx为别名二:
typedef void (*pFunx)(pFunParam);
原声明的最简化版:
pFunx b[10];
3. 原声明:doube(*)() (*e)[9];
变量名为e,先替换左边部分,pFuny为别名一:
typedef double(*pFuny)();
再替换右边的变量e,pFunParamy为别名二
typedef pFuny (*pFunParamy)[9];
原声明的最简化版:
pFunParamy e;
理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:
int (*func)(int *p);
首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int。
int (*func[5])(int *);
func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。
也可以记住2个模式:
type (*)(....)函数指针
type (*)[]数组指针
---------------------------------
陷阱一:
记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:
先定义:
typedef char* PSTR;
然后:
int mystrcmp(const PSTR, const PSTR);
const PSTR实际上相当于const char*吗?不是的,它实际上相当于char* const。
原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。
简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。
陷阱二:
typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:
typedef static int INT2; //不可行
编译将失败,会提示“指定了一个以上的存储类”。