【c/c++】 宏定义define和const用法详解


在写程序时经常会碰到这样一个问题,我们需要 重复写很多相同的代码,并且这些 代码结构相同。总是想自己把这段代码封装一下然后直接进行调用,但是如果这段代码逻辑并不复杂,并且代码量也不大,不适合进行封装,那么我们就会想到c/c++中的关键字 define。其实明智的你,遇到上面的这些情况估计还会想到另一个东西---- template,后面我们也会说一下两者的却别。今天自己看了一下c++ define的相关知识,做个记录。

一. define

宏定义,简单的理解就是替换,其实这也是本质。如果熟悉g++编译过程的话,会了解到一个概念叫做预处理,就是在编译之前做个处理。这个过程并不像编译那么复杂,就是简单的递归替换删除替换的就是宏定义和include文件,删除注释。注意这里我们谈到一个概念,递归替换,这个其实是很常见的,比如你的程序中include一个.h文件,但是这个.h文件中还引入了另一个.h文件,那么这个时候就需要进行递归替换。

1. define的使用方法:

  • 简单使用:define identifier replacement-list(optional) 这是最基本的使用方法,就像上面我们的例子一下,在预处理的时候将identifier替换成replacement-list(optional)。比如:在开发程序的时候会定义很多错误码,比如使用22表示图片上传成功,如果我们这样进行判断assert(result==22)。看似是没什么问题,那么要是你看了谁的代码是这样写的,你会怎么想?谁写的,这22是什么意思? 这时候就需要我们给22起一个别名,例如: #define Success = 22
  • 带参形式:除了上面的使用方法我们还可以给定参数来进行宏定义,比如:
#include<iostream>
#include<string.h>//定义了一个宏,将A,B进行连接
#define Append(A,B) A+B;
using namespace std;
int main(int argc, char *argv[])
{   
    string test1 = "1";
    string test2 = "2";  //进行宏定义的调用
    cout<<Append(test1,test2);
    return 0;
}

从这个例子我们可以看出宏定义的好处之一:可以在一定程度上忽略宏定义中参数的类型,这一点是不是和template很相似,我们使用template来重写一下上面的这段代码

#include<iostream>
#include<string.h>
using namespace std;//定义模板方法
template<class T>
T append(T a,T b){
    return a+b;
}
int main(int argc, char *argv[])
{
    string A = "1";
    string B = "2";
    //使用模板方法   cout<<append(A,B);      
    return 0;
}

这里我们是不是会觉得在这种情况下面宏定义与template是一致的?其实并不是,我们考虑一下下面的这种情况,假设存在这样一个宏定义#define add(a,b) a+b,并且在代码中调用过程是这样的a*add(a,b)b;大家可以想想结果是多少?答案是5,而不是我们预期的6,原因很简单,这只是简单的替换,经过预处理之后的结果是aa+bb*。但是template运算的结果就是6,这就是差别。所以在使用的时候还是应该考虑是选择template还是define。

  • define中代码段的表示: 上面我们说到的宏定义都是简单的一条语句,如果我们需要定义一个稍微复杂一点的语句呢?比如:我们习惯,或者说是个人习惯,喜欢把一些判断语句进行宏定义,比如我们在进行一个功能错误码的校验的时候通常希望能够将代码和log日志分开,使得代码显得不那么凌乱,那么我们将会定义一个稍微复杂一点的宏定义。
#include<iostream>
#include<string.h>
//宏定义一个MaxName
#define MaxName 10
//宏定义   定义了一个判断条件用于识别图片上传之后的返回码 并输出日志条件
#define AssertReturnValue(name,result)do{\
    if(result==0)\
        cout<<"图片"<<name<<"上传成功";\
}while(0)

using namespace std;

class Picture
{
public:
    Picture (const char *path,const char *file){
        strcpy(this->path,path);
        strcpy(this->file,file);
    }
    ~Picture (){
            
    }

private:
    char file[MaxName];
    char path[MaxName];
};
int uploadPicture(const Picture picture)
{
    cout<<"成功";
    return 0;
}
int main(int argc, char *argv[])
{
    Picture picture("100","100");
    int result = uploadPicture(picture);  
    //调用结果判断宏定义
    AssertReturnValue("100",result);
    return 0;
}

这里我们应该注意一下,代码段的宏定义的写法。

  • 条件宏定义ifdef:可以在编译的时候通过#define设置编译环境,语法结构:
//不同的运行环境,不同的头文件
#ifdef OS_Win
#include <windows.h>
#endif

#ifdef OS_Linux
#include <linux.h>
#endif

下面我们将c++ 常用的宏定义罗列一下:

# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if……#else条件编译块
#error 停止编译并显示错误信息

参考http://www.cnblogs.com/zi-xing/p/4550246.html

2. define作用

2.1. 简单的define定义

#define MAXTIME 1000

一个简单的MAXTIME就定义好了,它代表1000,如果在程序里面写

if(i<MAXTIME){.........}

编译器在处理这个代码之前会对MAXTIME进行处理替换为1000。

这样的定义看起来类似于普通的常量定义CONST,但也有着不同,因为define的定义更像是简单的文本替换,而不是作为一个量来使用,这个问题在下面反映的尤为突出。

2.2. define的“函数定义”

define可以像函数那样接受一些参数,如下

#define max(x,y) (x)>(y)?(x):(y);

这个定义就将返回两个数中较大的那个,看到了吗?因为这个“函数”没有类型检查,就好像一个函数模板似的,当然,它绝对没有模板那么安全就是了。可以作为一个简单的模板来使用而已。

但是这样做的话存在隐患

  • 例子如下:
#define Add(a,b) a+b;

在一般使用的时候是没有问题的,但是如果遇到如:c * Add(a,b) * d的时候就会出现问题,代数式的本意是a+b然后去和c,d相乘,但是因为使用了define(它只是一个简单的替换),所以式子实际上变成了
ca + bd

  • 另外举一个例子:
#define pin (int*);
pin a,b;

本意是a和b都是int型指针,但是实际上变成int* a,b;
a是int型指针,而b是int型变量
这是应该使用typedef来代替define,这样a和b就都是int型指针了。

所以我们在定义的时候,养成一个良好的习惯,

建议所有的层次都要加括号。

2.3. 宏的单行定义

#define Conn(x,y) x##y           表示连接字符串

#define ToChar(x) #@x            表示加上单引号

#define ToString(x) #x             表示加上双引号

我们假设:x=1,则有:

Conn(1)------〉T_1
ToChar(1)------〉‘1’
ToString(1)------〉“1”

2.4. define的多行定义

define可以替代多行的代码,例如MFC中的宏定义(非常的经典,虽然让人看了恶心)

#define MACRO(arg1, arg2) do { /
/* declarations */ /
stmt1; /
stmt2; /
/* ... */ /
} while(0) /* (no trailing ; ) */

关键是要在每一个换行的时候加上一个"/"

2.5. 条件编译

4.在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的功能是条件编译。

就是:

#ifdef WINDOWS
......
......
#endif
#ifdef LINUX
......
......
#endif

可以在编译的时候通过#define设置编译环境

#ifdef XXX…(#else) … #endif

例如
#ifdef DV22_AUX_INPUT
#define AUX_MODE 3 
#else
#define AUY_MODE 3
#endif
#ifndef XXX … (#else) … #endif

2.6. 定义宏,取消宏

//定义宏
#define [MacroName] [MacroValue]
//取消宏
#undef [MacroName]
//普通宏
#define PI (3.1415926)

带参数的宏
#define max(a,b) ((a)>(b)? (a),(b))

关键是十分容易产生错误,包括机器和人理解上的差异等等。

2.7. 头文件(.h)可以被头文件或C文件包含;

重复包含(重复定义)
由于头文件包含可以嵌套,那么C文件就有可能包含多次同一个头文件两个头文件循环调用 就可能出现重复定义的问题

  • 在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用# ifndef宏定义,一个c文件多次包含同一个h文件也不会报错。
  • 但如果你在头文件A里定义了结构体或者类类型(这是最常见的情况),在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错.

通过条件编译开关来避免重复包含(重复定义)

可参考 链接

例如
#ifndef __headerfileXXX__
#define __headerfileXXX__//文件内容#endif

以上只是我从网络上搜集了一些关于define的一些用法,可能还不全面。

二、const和inline

在c++中,

  • 定义常变量,应该用const定义,
  • 定义常用函数,应该用 inline 定义,

这两种方法更好。

1. inline函数和用macro定义的函数区别

  • macro定义

只是很初级的一种代换,实现的功能很单一
而且安全性很差,比如类型错误、括号漏写 都会造成很大的错误,
而且错误不容易被发现,隐患很大

  • inline函数

内联函数要比前者好很多 功能也要全面很多!
最主要的是 内联函数能够进行安全检查(比如参数类型 等)
如果在能够使用两着的情况之下 推荐使用 内联

  不过有两点要注意: 
  1. 内联 是以代码膨胀为代价的,
    不是所有的函数都适合用 内联 方式
    要考虑函数的实际情况
  2. macro定义 也不是说一无是处了
    在合适的时候使用 也许会有意想不到的效果

2. const 与 #define的区别

最大的差别

  • 前者在堆栈分配了空间,而后者只是把具体数值直接传递到目标变量罢了。
  • 或者说,const的常量是一个Run-Time的概念,他在程序中确确实实的存在可以被调用、传递。
  • 而#define常量则是一个Compile-Time概念,它的生命周期止于编译期:在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。
  • const常量存在于程序的数据段.
  • #define常量存在于程序的代码段。

优缺点:

至于两者的优缺点,要看具体的情况了。一般的常数应用,我个人认为#define是一个更好的选择

  • 从run-time的角度来看,#define 在空间上和时间上都有很好优势。

  • 从compile-time的角度来看,类似m=t10的代码不会被编译器优化,t10的操作需要在run-time执行。而#define的常量会被合并。

  • 但是 如果你需要粗鲁的修改常数的值,那就的使用const了,因为后者在程序中没有实际的存在.

  • 另外在头文件中使用 #define 可以避免头文件重复包含的问题,这个功能,是const无法取代的。

3. 成员函数末尾的const

const: 常量,在成员函数后面增加一个const。不单要在成员函数声明中增加const,也要在函数定义中增加const。
作用: 告诉系统,这个函数,不会修改对象里的任何成员变量的值。

成员函数后面加const的成员函数也称为“常量成员函数”。
如果在编写const成员函数时,不小心作了修改数据成员的操作,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

被const声明的成员函数的几点规则:

  1. 在类中被const声明的成员函数只能访问const成员函数,而非const函数可以访问任意的成员函数,包括const成员函数。
  2. 在类中被const声明的成员函数不可以修改对象的数据.它在编译时,以是否修改成员数据为依据,进行检查。
  3. 加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的
  4. 普通函数(非成员函数)后面不能放const。const意思是成员函数值不能改变,普通函数没有成员函数。
  5. const函数都能被任意类型对象调用。const 对象只能调用const函数。
    换种说法: const成员函数可以被const对象和非const对象调用;非const成员函数只能被非const对象调用,不能被const对象调用。

代码示例

class Time {
public:
	int hour;
	int minute;
	int second;
	//成员函数
public:
	void addhour(int temphour) const;
};


void Time::addhour(int temphour) const {
	hour += temphour;//报错,不能修改
}
class Time {
public:
	int hour;
	int minute;
	int second;
	//成员函数
public:
	void addhour(int temphour) {
		hour += temphour;
	}
	void nooe()const {};
};


const Time abc;
abc.addhour(10);//报错
abc.nooe();//正确
Time time2;
time2.addhour(10);//正确
time2.nooe();//正确
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值