C语言使用相关汇总3

内容包括宏的使用、C语句中的@、符号优先级、结构体的声明与调用。紫色文字为超链接,点击自动跳转至其他相关博文。持续更新,原创不易!

目录:

19、宏的使用

1)概述   2)关于#和##  3)关于...的使用

20、C语句中的@

21、C语言符号优先级

22、整型1除以表达式,1改成1.0

23、结构体声明(extern)与调用

24、变量命名规则

1)驼峰命名规则   2)匈牙利命名规则   3)帕斯卡命名规则

25、#define中的UL

26、声明和定义全局变量与函数

100、C语言学习推荐


19、宏的使用

1)概述

C/C++语言中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题做了简单总结。C语言使用相关汇总218、typedef和#define的用法与区别。

2)关于#和##

测试程序移步:宏的#与##(C-Free代码)

(1)在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。比如下面代码中的宏:

#define WARN_IF(EXP)\
   do{ if (EXP)\
   fprintf(stderr, "Warning: " #EXP "\n");\
}while(0)

下面用divider == 0代替EXP,那么实际使用中会出现下面所示的替换过程:

WARN_IF (divider == 0);  被替换为
do {
   if (divider == 0)
       fprintf(stderr, "Warning: " "divider == 0" "\n");
} while(0);

这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。

--------------------

(2)而##被称为连接符(concatenator [knkætnet]),用来将两个子串Token[tkn]连接为一个Token注意这里连接的对象是Token就行,而不一定是宏的变量。比如要做一个菜单项命令名和函数指针(指针函数与函数指针的区别)组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:

#include "iostream"

void quit_command( )
{
    printf("I am quit command\n");
}   

void help_command( )
{
    printf("I am help command\n");
}

struct command
{
    char * name; //指针变量name
    void (*function) (void); //函数指针本身首先应是指针变量,只不过该指针变量function指向函数
};  

#define COMMAND(NAME) {#NAME,NAME##_command}
#define PRINT(NAME) printf("token"#NAME"=%d\n", token##NAME)
int main(void)
{
    int token9=9;
    PRINT(9);

    struct command commands[ ] = 
    {
        COMMAND(quit),   //等同于{ "quit", quit_command },
        COMMAND(help),  //等同于{ "help", help_command },
    };
    commands[0].function( );  //指针指向quit_command( )函数
}

说明:

调用时候使用: PRINT(9);
宏展开即为: printf("token"#9"=%d\n",token##9);
#9即为"9",token##9即为: token9

注意到在这个例子中PRINT(9);中的这个”9”被原封不动的当成了一个字符串,与”token”连接在了一起,从而成为了token9。
整个为: printf("token""9""=%d\n",token9);  之前定义过token9为9,所以就是输出 token9=9;

COMMAND宏定义是有{ }的,第一个#NAME,就是赋值给结构体command的char *name,第二个 NAME##_command,用来拼出函数名,赋值给结构体中的函数指针,之后在commands[0].function( )中通过函数指针来调用函数,输出I am quit command

我们还可以n个##符号连接n+1个Token,这个特性也是#符号所不具备的。比如:

#define  LINK_MULTIPLE(a,b,c,d)  a##_##b##_##c##_##d
typedef  struct _record_typeLINK_MULTIPLE(name,company,position,salary);

// 这里这个语句将展开为:typedef  struct _record_type  name_company_position_salary;

3)关于...的使用

...在C宏中称为Variadic Macro,也就是变参宏。比如:#define myprintf(templt,...)fprintf(stderr,templt,__VA_ARGS__)

或者 #define myprintf(templt,args...) fprintf(stderr,templt,args)

第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:myprintf(templt,);的形式。这时的替换过程为:

myprintf("Error!\n",);  替换为: fprintf(stderr,"Error!\n",);

这是一个语法错误,不能正常编译。这个问题一般有 两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

myprintf(templt);  而它将会 被通过替换变成: fprintf(stderr,"Error!\n",);

很明显,这里仍然会产生编译错误(非本例的 某些情况下不会产生编译错误)。除了这种方式外,c99和GNUCPP都支持下面的宏定义方式:  #define myprintf(templt, ...) fprintf(stderr,templt,##__VAR_ARGS__)

这 时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:

myprintf(templt);  被转化为:  fprintf(stderr,templt);

这样如果templt合法,将不会产生 编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

错误的嵌套-Misnesting

宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

由 操作符优先级引起的问题-Operator Precedence Problem

由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:

#define ceil_div(x, y) (x + y - 1) / y

那么 a = ceil_div( b & c, sizeof(int) );

将被转化为:a = ( b & c + sizeof(int) - 1) / sizeof(int);

// 由于+/-的优先级高于&的优先级,那么上面式子等同于: a = ( b & (c + sizeof(int) - 1)) /sizeof(int);

这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:

#define ceil_div(x, y) (((x) + (y) - 1) / (y))

消除多余的分号-Semicolon Swallowing

通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

MY_MACRO(x);

但是如果是下面的情况:

#define MY_MACRO(x) {
\
}
//...
if (condition())
    MY_MACRO(a);
else
    {...}

这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

#define MY_MACRO(x) do {
\
} while(0)

这样只要保证总是使用分号,就不会有任何问题。

Duplication of Side Effects

这里的SideEffect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:

#define min(X,Y) ((X) > (Y) ? (Y) : (X))
//...
c = min(a,foo(b));

这 时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:

#define min(X,Y) ({ \
typeof (X) x_ = (X); \
typeof (Y) y_ = (Y); \
(x_ < y_) ? x_ : y_; })

({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

20、C语句中的@

union tagSTATUS
{
  unsigned char uc;

  struct
  {
    unsigned char C:1;
    unsigned char AC:1;
    unsigned char Z:1;
    unsigned char OV:1;
    unsigned char PDF:1;
    unsigned char TO:1;
    unsigned char UNUSED0:1;
    unsigned char UNUSED1:1;
  } b;
};

unsigned char _rSTATUS       @0x0a;  //0x0a=STATUS
union tagSTATUS _ruSTATUS @0x0a;  //0x0a=STATUS

@是给这个定义的变量定义一个起始地址,也就是说_rSTATUS这个变量的开始地址是0x0a。此处@的含义跟汇编语言里面的一个伪指令功能一样:“at”。

21、C语言符号优先级

说明:

同一优先级的运算符,运算次序由结合方向所决定。简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符。

22、整型1除以表达式,1改成1.0

C程序中,整数除以整数,得到的还是整数,如5/3=1,而不是1.6

小数除以整数,或者整数除以小数,那么得到的是小数。也就是说,C程序中,向后面看齐。

23、结构体声明(extern)与调用

在.h文件中这样定义:

typedef struct typFNT_GB16       // 汉字字模数据结构
{
    signed char Index[2];        // 汉字内码索引
    char Msk[32];                // 点阵码数据
};

struct typFNT_GB16 code GB_16[] =     // 数据表
{
    0x20,0x24,0x24,0x24,0xFE,0x23,0x22,0x20,
    0xFF,0x20,0x22,0xAC,0x20,0x30,0x20,0x00,
    0x00,0x08,0x48,0x84,0x7F,0x02,0x21,0x10,
    0x09,0x06,0x1A,0x61,0x80,0xE0,0x00,0x00,
    0x40,0x20,0xF8,0x07,0x00,0xF8,0x02,0x04,
    0x08,0x04,0x04,0x04,0x04,0xFE,0x04,0x00,
    0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,
    0x00,0x00,0x00,0x40,0x80,0x7F,0x00,0x00
};

同时需要多个.c文件中使用该struct,但是多次include该.h文件提示重复定义,应该修改如下:

新建一个xx.h和xx.c文件

在xx.h文件中定义该结构体,同时对结构体变量作extern:

typedef struct typFNT_GB16          // 汉字字模数据结构
{
    signed char Index[2];           // 汉字内码索引
    char Msk[32];                   // 点阵码数据
};
extern struct typFNT_GB16 code GB_16[]

在xx.c文件中include该.h文件,同时作结构体变量的初始化:

#include "xx.h"

struct typFNT_GB16 code GB_16[] =         // 数据表
{
    0x20,0x24,0x24,0x24,0xFE,0x23,0x22,0x20,
    0xFF,0x20,0x22,0xAC,0x20,0x30,0x20,0x00,
    0x00,0x08,0x48,0x84,0x7F,0x02,0x21,0x10,
    0x09,0x06,0x1A,0x61,0x80,0xE0,0x00,0x00,
    0x40,0x20,0xF8,0x07,0x00,0xF8,0x02,0x04,
    0x08,0x04,0x04,0x04,0x04,0xFE,0x04,0x00,
    0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,
    0x00,0x00,0x00,0x40,0x80,0x7F,0x00,0x00
};

在其他.c文件中需要使用该结构体时,直接inclue.h文件即可这样编译通过。实际上结构体是一种数据类型,.h文件定义了一种类型的结构体,并声明为extern形式允许外部调用它,而初始化code GB_[]这个结构体应当在.c文件中进行。

类型的定义和类型变量的定义不同,类型定义只是描述一个类型,是给编译器看的,不会产生可执行代码。变量定义是指在执行文件中真实的存在这么一块内容。因此,类型定义可以重复出现也没关系,类型变量是不能在多个.c中出现,否则就是重复定义。因为每个.c里都要写清楚类型定义很麻烦,所以一般都把类型定义写在.h里,而在.c里采用简单的写法。

24、变量命名规则

1)驼峰命名规则

也称骆驼式命名法(Camel)正如它的名称所表示的那样,是指混合使用大小写字母来构成变量和函数的名字。

例如下面是分别用骆驼式命名法和下划线法命名的同一个函数:

printEmployeePaychecks();
print_employee_paychecks();

骆驼式命名法近年来越来越流行了,在许多新的函数库和Microsoft Windows这样的环境中,它使用得相当多。下划线法是C出现后开始流行起来的,在许多旧的程序和UNIX这样的环境中,它的使用非常普遍。

驼峰式命名法分为:

大驼峰式命名规则:FirstName, CamelCase

小驼峰式命名规则:firstName, camelCase

[中间不需要空格、-、_等分割符]

2)匈牙利命名规则

Hungarian命名法广泛应用于象Microsoft Windows这样的环境中 Windows 编程中用到的变量(还包括宏)的命名规则匈牙利命名法,这种命名技术是由一位能干的 Microsoft 程序员查尔斯- 西蒙尼(Charles Simonyi) 提出。

匈牙利命名法通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域、类型等这些符号。

可以多个同时使用,顺序是先m_(成员变量), 再指针,再简单数据类型,再其它。

例如:m_lpszStr, 表示指向一个以0字符结尾的字符串的长指针成员变量。

匈牙利命名法关键是:标识符的名字以一个或者多个小写字母开头作为前缀;前缀之后的是首字母大写的一个单词或多个单词组合,该单词要指明变量的用途。

匈牙利命名法的规则:属性+类型+描述

属性一般是“小写字母”加“_”共同组成,如下。

g_全局变量
m_类成员变量
s_静态变量
c_常量
bbool
sz以零结束的字符串
p指针
n整型
dw双字
l长整型
无符号u
函数fn

3)帕斯卡命名规则

Pascal与骆驼命名法类似,只不过骆驼命名法是首字母小写,而帕斯卡命名法是首字母大写。

DisplayInfo(); 

string UserName;

pascal命名规则:大驼峰式命名规则

25、#define中的UL

U和L是整数文字量的后缀修饰,用于显示指明整数文字量的类型为unsigned int(U)和long int(L)。  
类似的还有浮点数文字量的后缀修饰F或f,用于指明文字量表示的是一个float,而不是默认情况下的double。
UL是标记该宏是长整型“十进制”数据,而不是字符,也不是int型数据。C语言中默认宏中的数字是整型数据。

#include <stdio.h>

#define SECONDS_PER_YEAR  60*60*24*365UL
int main(void)
{
    unsigned long int  a = SECONDS_PER_YEAR;
    printf("a = %ld/n",a);
    return 0;
}

26、声明和定义全局变量与函数

.c与.h文件配对出现形成代码模块,.c用于代码实现,.h用于变量和函数的统一声明。

1)code.c

int a = 1;
int b = 2;

int function1(int x)
{
    return x;
}

2)code.h

#ifndef __CODE_H
#define __CODE_H

extern int a;
int function1(int);
 
#endif 

3)调用子函数

#include "code.h"

void main(void)
{
    printf(%d",a);
}

100、C语言学习推荐

1)书籍推荐

《CPU眼里的C/C++》

------------------------------------

2)视频推荐

(1)翁凯c语言

基础:浙江大学-翁恺-教你C语言基础入门!进阶:浙江大学-翁恺-C语言程序设计进阶

(2)小甲鱼

零基础入门学习C语言——带你学C带你飞

(3)鹏哥C语言

C语言程序设计从入门到进阶


青春时代是一个短暂的美梦,当你醒来时,它早已消失得无影无踪。觉得不错,动动发财的小手点个赞哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱上电路设计

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值