C++中宏的使用问题详解

宏不遵循C++中关于范围和类型的规则。这经常导致一些微妙的或不那么微妙的问题。因此,C++提供更适合其他的C++(译注:原文为the rest of C++,当指C++除了兼容C 以外的部分)的替代品,例如内联函数、模板与名字空间。

考虑一下:

#include "someheader.h"
struct S {
  int alpha;
  int beta;
};

如果某人(不明智地)地写了一个叫“alpha”或“beta”的宏,那么它将不会被编译,或者被错误地编译,产生不可预知的结果。例如,“someheader.h”可能包含:

  #define alpha 'a'
  #define beta b[2]

将宏(而且仅仅是宏)全部大写的习惯,会有所帮助,但是对于宏并没有语言层次上的保护机制。例如,虽然成员的名字包含在结构体的内部,但这无济于事:在编译器能够正确地辨别这一点之前,宏已经将程序作为一个字符流进行了处理。顺便说一句,这是C 和C++程序开发环境和工具能够被简化的一个主要原因:人与编译器看到的是不同的东西。

不幸的是,你不能假设别的程序员总是能够避免这种你认为“相当白痴”的事情。例如,最近有人报告我,他们遇到了一个包含goto 的宏。我也见过这种情况,而且听到过一些——在很脆弱的时候——看起来确实有理的意见。例如:

#define prefix get_ready(); int ret__
#define Return(i) ret__=i; do_something(); goto exit
#define suffix exit: cleanup(); return ret__
void f(){
  prefix;
  // ...
  Return(10);
  // ...
  Return(x++);
  //...
  suffix;
}

作为一个维护的程序员,就会产生这种印象;将宏“隐藏”到一个头文件中——这并不罕见——使得这种“魔法”更难以被辨别。

一个常见的微妙问题是,一个函数风格的宏并不遵守函数参数传递的规则。例如:

#define square(x) (x*x)
void f(double d, int i){
  square(d); // 好
  square(i++); // 糟糕:这表示 (i++*i++)
  square(d+1); //糟糕:这表示(d+1*d+1); 也就是 (d+d+1)
  // ...
}

“d+1”的问题,可以通过在“调用”时或宏定义时添加一对圆括号来解决:

  #define square(x) ((x)*(x)) /*这样更好 */

但是, i++被执行了两次(可能并不是有意要这么做)的问题仍然存在。

是的,我确实知道有些特殊的宏并不会导致C/C++预处理宏这样的问题。但是,我无心去发展C++中的宏。作为替代,我推荐使用C++语言中合适的工具,例如内联函数,模板,构造函数(用来初始化),析构函数(用来清除),异常(用来退出上下文环境),等等。

好了,今天就先到这里,以后我们再来更深入的探讨这个问题

(0)

相关推荐

  • C++十六进制宏的用法详解

    流行的用法:用二进制的每一位代表一种状态. 001,010,100这样就表示三种状态. 通过或|运算就可以组合各种状态. 001|010=011 001|010|100=111 通过与&运算可以去除某种状态. 111&001=110 可以定义这样的宏组合成函数的参数 #defineP10x001L//001 #defineP20x002L//010 #defineP30x004L//100 voidFunc(long){} Func(P1|P2); 可以这样判断某位是否是1 由于001与x

  • C/C++宏定义的可变参数详细解析

    编写代码的过程中,经常会输出一些调试信息到屏幕上,一般会调用printf这类的函数.但是当调试解决之后,我们需要手工将这些地方删除或者注释掉.最近在看<Linux C编程一站式学习>这本书,就想到一个方法: 复制代码 代码如下: void myprintf(char* fmt, ...){}#ifdef DEBUG#define printf(fmt, args...) myprintf(fmt, ##args)#endif 调试阶段带着DEBUG调试,正式上线就可以把printf变成一个空函

  • 简单讲解C++的内部和外部函数以及宏的定义

    C++内部函数和外部函数 函数本质上是全局的,因为一个函数要被另外的函数调用,但是,也可以指定函数只能被本文件调用,而不能被其他文件调用.根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数. 内部函数 如果一个函数只能被本文件中其他函数所调用,它称为内部函数.在定义内部函数时,在函数名和函数类型的前面加static.函数首部的一般格式为: static 类型标识符 函数名(形参表); 如 static int fun(int a, int b); 内部函数又称静态(static)函数.

  • 在C++中自定义宏的简单方法

    可以使用宏定义没有返回值的"函数".例如: 复制代码 代码如下: #define PrintMax(a, b) \   do \   { \     int x = a, y = b; \     printf("Max: %d\n", x > y ? x : y);\   } while (0) // ... PrintMax(3, 4); 这样的"函数"与真正意义上的函数有本质的区别,因为宏是一个编译前行为,仅仅是编译前对文本进行替换.

  • C/C++ 宏详细解析

    众多C++书籍都忠告我们C语言宏是万恶之首,但事情总不如我们想象的那么坏,就如同goto一样.宏有一个很大的作用,就是自动为我们产生代码.如果说模板可以为我们产生各种型别的代码(型别替换),那么宏其实可以为我们在符号上产生新的代码(即符号替换.增加). 关于宏的一些语法问题,可以在google上找到.相信我,你对于宏的了解绝对没你想象的那么多.如果你还不知道#和##,也不知道prescan,那么你肯定对宏的了解不够. 我稍微讲解下宏的一些语法问题(说语法问题似乎不妥,macro只与preproc

  • c++ 编程 几个有用的宏详解

    1. 打印错误信息 如果程序的执行必须要求某个宏被定义,在检查到宏没有被定义是可以使用#error,#warning打印错误(警告)信息,如: #ifndef __unix__ #error "This section will only work on UNIX systems" #endif 只有__unix__宏被定义,程序才能被正常编译. 2. 方便调试 __FILE, __LINE, __FUNCTION是由编译器预定义的宏,其分别代表当前代码所在的文件名,行号,以及函数名.

  • C++中宏的使用问题详解

    宏不遵循C++中关于范围和类型的规则.这经常导致一些微妙的或不那么微妙的问题.因此,C++提供更适合其他的C++(译注:原文为the rest of C++,当指C++除了兼容C 以外的部分)的替代品,例如内联函数.模板与名字空间. 考虑一下: #include "someheader.h" struct S { int alpha; int beta; }; 如果某人(不明智地)地写了一个叫"alpha"或"beta"的宏,那么它将不会被编译,

  • C/C++语言宏定义使用实例详解

     C/C++语言宏定义使用实例详解 1. #ifndef 防止头文件重定义 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成 一个可执行文件时,就会出现大量"重定义"的错误.在头文件中实用#ifndef #define #endif能避免头文件的重定义. 方法:例如要编写头文件test.h 在头文件开头写上两行: #ifndef TEST_H #define TEST_H //一般是文件名的大写 头文件结尾写上一行: #endif 这样一个工程文件里同时

  • C语言中强制地址跳转详解

    C语言中强制地址跳转详解 #define jump(TargetAddr ) (*((void(*)())(TargetAddr))() 第一个(( void( * )(  )) ,意思为强制类型转换为一个无形参,无返回值的函数指针,(*(TargetAddr))为跳转地址,但是函数指针变量不能为常数所以要加((void( * )(  )) 进行强制类型转换.最后一个()为执行的意思. 整一条指定的目的是为了跳转到一个绝对地址执行函数. 1.在单片机中可以实现软件复位,比如跳转到0地址. 2.如

  • 基于python中的TCP及UDP(详解)

    python中是通过套接字即socket来实现UDP及TCP通信的.有两种套接字面向连接的及无连接的,也就是TCP套接字及UDP套接字. TCP通信模型 创建TCP服务器 伪代码: ss = socket() # 创建服务器套接字 ss.bind() # 套接字与地址绑定 ss.listen() # 监听连接 inf_loop: # 服务器无限循环 cs = ss.accept() # 接受客户端连接 comm_loop: # 通信循环 cs.recv()/cs.send() # 对话(接收/发

  • Oracle中游标Cursor基本用法详解

    查询 SELECT语句用于从数据库中查询数据,当在PL/SQL中使用SELECT语句时,要与INTO子句一起使用,查询的 返回值被赋予INTO子句中的变量,变量的声明是在DELCARE中.SELECT INTO语法如下: SELECT [DISTICT|ALL]{*|column[,column,...]} INTO (variable[,variable,...] |record) FROM {table|(sub-query)}[alias] WHERE............ PL/SQL

  • JDBC中resutset接口操作实例详解

    本文主要向大家展示JDBC接口中resutset接口的用法实例,下面我们看看具体内容. 1. ResultSet细节1 功能:封锁结果集数据 操作:如何获得(取出)结果 package com.sjx.a; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import org.junit.Test; //1. next方

  • AngularJS中filter的使用实例详解

    AngularJS中filter的使用实例详解 一.格式化数字为货币格式. <div>{{money|currency:"$"}}</div> <div>{{money|currency:"RMB"}}</div> script: app.controller("crl", function($scope, $filter) { $scope.money="4545"; });

  • python中模块的__all__属性详解

    python模块中的__all__属性,可用于模块导入时限制,如: from module import * 此时被导入模块若定义了__all__属性,则只有__all__内指定的属性.方法.类可被导入. 若没定义,则导入模块内的所有公有属性,方法和类 # kk.py class A(): def __init__(self,name,age): self.name=name self.age=age class B(): def __init__(self,name,id): self.nam

  • python编程之requests在网络请求中添加cookies参数方法详解

    哎,好久没有学习爬虫了,现在想要重新拾起来.发现之前学习爬虫有些粗糙,竟然连requests中添加cookies都没有掌握,惭愧.废话不宜多,直接上内容. 我们平时使用requests获取网络内容很简单,几行代码搞定了,例如: import requests res=requests.get("https://cloud.flyme.cn/browser/index.jsp") print res.content 你没有看错,真的只有三行代码.但是简单归简单,问题还是不少的. 首先,这

  • PHP 中魔术常量的实例详解

    PHP 中魔术常量的实例详解 本文介绍下,php编程中的魔术常量,掌握并灵活应用这些方法与常量,对于提高php的编程水平,有很大的帮助.有需要的朋友参考学习下. 魔术常量: namespace ns1; class Test { function __construct() { var_dump(__LINE__); var_dump(__FILE__); var_dump(__DIR__); var_dump(__FUNCTION__); var_dump(__CLASS__); var_du

随机推荐