本文将学习进阶阶段的最后一些内容了,程序环境和预处理,主要内容包括:
在ANSI C的任何一种实现中,存在两个不同的环境:
//sum.c
int g_val = 2022;
void print(const char *str)
{
printf("%s\n", str);
}
//test.c
int main()
{
extern void print(char *str);
extern int g_val;
printf("%d\n", g_val);
print("hello bit.\n");
return 0;
}
test.c
int main()
{
int i = 0;
for(i=0; i<10; i++)
{
printf("%d ", i);
}
return 0;
}
使用linux gcc查看:
预定义符号都是语言内置的:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
printf("file:%s line:%d\n", __FILE__, __LINE__);
#define name stuff //定义
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中:
用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用:
#define SQUARE(x) (x) * (x) //需要注意多加括号
printf ("%d\n",(a + 1) * (a + 1) );
在程序中扩展#define定义符号和宏时,需要涉及几个步骤:
注意:
char* p = "hello ""bit\n";
printf("hello"," bit\n");
printf("%s", p);
#define PRINT(n) printf("the value is "#n" is %d\n", n)
int main()
{
int a = 10;
PRINT(a);
//相当于printf("the value of ""a"" is %d\n", a)
return 0;
}
#define PRINT(N, format) printf("the value of "#N" is "format"\n", N)
int main()
{
int a = 20;
double pai = 3.14;
PRINT(a, "%d");
PRINT(pai, "%lf");
return 0;
}
#define cat(name,num) name##num
int main()
{
int class105 = 105;
printf("%d\n", cat(class, 105));
return 0;
}
具有副作用的参数所引起的问题:
x+1;//不带副作用
x++;//带有副作用
#define MAX(x, y) ((x)>(y)?(x):(y))
int main()
{
int a = 5;
int b = 8;
int c = MAX(a++, b++);
//int c = ((a++) > (b++) ? (a++) : (b++));
printf("%d\n", c);//
printf("%d\n", a);//
printf("%d\n", b);//
return 0;
}
宏:
#define MAX(x, y) ((x)>(y)?(x):(y))
int main()
{
int a = 0;
int b = 20;
int c = 0;
c = MAX(a, b);
return 0;
}
函数:
int Max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 0;
int b = 20;
int c = 0;
c = MAX(a, b);
return 0;
}
#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));
宏和函数的对比:
#define定义宏 | 函数 | |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果 | 函数参数只在传参的时候求值一次,结果更容易控制 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
这条指令用于移除一个宏定义
#undef NAME
#define MALLOC(num ,type) (type*)malloc(num*sizeof(type))
int main()
{
int*p = (int*)malloc(10 * sizeof(int));
int*p2 = MALLOC(10, int);
//int *p2 = (int*)malloc(10*sizeof(int));
#undef MALLOC
MALLOC(20, char);
return 0;
}
允许在命令行中定义符号。用于启动编译过程
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值,如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
#define NUM 8
int main()
{
#if NUM==1
printf("hehe\n");
#elif NUM == 2
printf("haha\n");
#else
printf("heihei\n");
#endif
return 0;
}
#include 指令可以使另外一个文件被编译,替换方式:
本地头文件,或者自定义头文件
#include "filename
查找策略:
Linux环境的标准头文件的路径:
/usr/include
VS环境的标准头文件的路径:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
库文件包含
#include
查找策略:
对于库文件也可以使用 “” 的形式包含,但是这样做查找的效率就低些,也不容易区分是库文件还是本地文件了。
这样最终程序中就会出现两份comm.h的内容,这样就造成了文件内容的重复。解决方法:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者
#pragma once
就可以避免头文件的重复引入。
#error
#pragma
#line
...
#pragma pack()在结构体部分介绍
本文的内容大家可以参考下面几本书:
经过基础阶段和进阶阶段的学习,推荐大家看 《C程序设计语言》(第2版.新版) :
到目前位置,C语言阶段的知识学习基本结束了。但是学过不代表会了,要经常复习,温故而知新。还要练习题目,巩固所学知识。推荐牛客网的在线编程的题目:牛客在线编程—基本语法。
将自己不熟悉的知识点记录下来,写成博客分享到 CSDN,形成一个良性循环,坚持总会有收获的。
下一个阶段开始学习新的内容了:初阶数据结构和算法,是用C语言实现。