预处理指令
- 1、宏定义 2、条件编译 3、文件包含
不带参数的宏定义
不带参数的宏定义定义方式:
- 列表内容一般形式:#define 宏名 字符串 比如:#define ABC 10
- 右边的字符串也可以省略,比如:#define ABC
不带参数的宏定义的作用:
- 它的作用是在编译预处理时,将源程序中所有“宏名”替换成右边的“字符串”,常用来定义常量
不带参数的宏定义使用习惯与注意
- 1、宏名一般用大写字母,以便与变量区别开来,但小写也没有语法错误。
- 2、对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作。比如:char *s = “ABC”
- 3、在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已展开宏名的源程序进行语法检查。
- 4、宏名的有效范围时从定义位置到文件结束。如果需要终止宏定义的作用域,可以用 #undef命令
#define PI 3.14
......
......
#undef PI
//定义一个宏时可以引用已经定义过的宏名
#define R 3.0
#define PI 3.14
#define L 2*PI*R
#define S PI*R*R
带参数的宏定义
带参数的宏定义的一般形式:
#define 宏名(参数列表) 字符串
带参数的宏定义作用:
在编译预处理时,将源程序中所有宏名替换成字符串,并且将 字符串中的参数 用 宏名右边参数列表 中的参数替换
#include <stdio.h>
#define average(a, b) (a+b)/2
int main ()
{
int a = average(10, 4);
printf("平均值:%d", a);
return 0;
}
带参数的宏定义使用注意:
- 1> 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串。
- 2> 带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。
- 3> 计算结果最好也用括号括起来。
带参数的宏定义与函数的区别
从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:
- 1> 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题
- 2> 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率
预处理指令2:条件编译
条件编译的概念:
- 在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。
条件编译基本用法:
#if 条件1
...code1...
#elif 条件2
...code2...
#else
...code3...
#endif
- 1> 如果条件1成立,那么编译器就会把#if 与 #elif之间的code1代码编译进去(注意:是编译进去,不是执行,很平时用的if-else是不一样的)
- 2> 如果条件1不成立、条件2成立,那么编译器就会把#elif 与 #else之间的code2代码编译进去
- 3> 如果条件1、2都不成立,那么编译器就会把#else 与 #endif之间的code3编译进去
- 4> 注意,条件编译结束后,要在最后面加一个#endif,不然后果很严重。
- 5> #if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义
例:
#include <stdio.h>
#define MAX 11
int main ()
{
#if MAX == 0
printf("MAX是0");
#elif MAX > 0
printf("MAX大于0");
#else
printf("MAX小于0");
#endif
return 0;
}
#if defined()和#if !defined()的用法:
- #if 和 #elif后面的条件不仅仅可以用来判断宏的值,还可以判断是否定义过某个宏。比如:
#if defined(MAX)
...code...
#endif
//条件也可以取反
#if !defined(MAX)
...code...
#endif
#ifdef和#ifndef的使用
- #ifdef的使用和#if defined()的用法基本一致
- #ifndef又和#if !defined()的用法基本一致
预处理指令3:文件包含
一般形式
- 1.第1种形式#include <文件名>
直接到C语言库函数头文件所在的目录中寻找文件 - 2.第2种形式 #include “文件名”
系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找
变量类型
变量的作用域:
- 1.局部变量
- 2.全局变量
变量的存储类型:
- 1.自动变量
- 2.静态变量
- 3.寄存器变量
自动变量
- 1> 定义:自动变量是存储在堆栈中的。
- 2> 哪些是自动变量:被关键字auto修饰的局部变量都是自动变量,但是极少使用这个关键字,基本上是废的,因为所有的局部变量在默认情况下都是自动变量。
- 3> 生命周期:在程序执行到声明自动变量的代码块(函数)时,自动变量才被创建;当自动变量所在的代码块(函数)执行完毕后,这些自动变量就会自行销毁。如果一个函数被重复调用,这些自动变量每次都会重新创建。
静态变量
- 列表内容
- 1> 定义:静态变量是存储在静态内存中的,也就是不属于堆栈。
- 2> 哪些是静态变量:
- 所有的全局变量都是静态变量
被关键字static修饰的局部变量也是静态变量 - 3> 生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束
寄存器变量
- 1> 定义:存储在硬件寄存器中的变量,称为寄存器变量。寄存器变量比存储在内存中的变量访问效率更高(默认情况下,自动变量和静态变量都是放在内存中的)。
- 2> 哪些变量是寄存器变量:
被关键字register修饰的自动变量都是寄存器变量
只有自动变量才可以是寄存器变量,全局变量和静态局部变量不行
寄存器变量只限于int、char和指针类型变量使用 - 3> 生命周期:因为寄存器变量本身就是自动变量,所以函数中的寄存器变量在调用该函数时占用寄存器中存放的值,当函数结束时释放寄存器,变量消失。
4> 使用注意:
由于计算机中寄存器数目有限,不能使用太多的寄存器变量。如果寄存器使用饱和时,程序将寄存器变量自动转换为自动变量处理
为了提高运算速度,一般会将一些频繁使用的自动变量定义为寄存器变量,这样程序尽可能地为它分配寄存器存放,而不用内存
static、extern与函数的总结
1.static
- 在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。
- static也可以用来声明一个内部函数
2.extern
- 在定义函数时,如果在函数的最左边加上关键字extern,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。
- 在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用extern声明该外部函数,然后就可以使用,这里的extern也可以省略。
static和extern与变量的总结
- extern可以用来声明一个全局变量,但是不能用来定义变量
- 默认情况下,一个全局变量是可以供多个源文件共享的,也就说,多个源文件中同名的全局变量都代表着同一个变量
- 如果在定义全局变量的时候加上static关键字,此时static的作用在于限制该全局变量的作用域,只能在定义该全局变量的文件中才能使用,跟其他源文件中的同名变量互不干扰
结构体
结构体的初始化
struct Student {
char *name;
int age;
};
struct Student stu = {"chenfanfang", 27};
//只能在定义变量的同时进行初始化赋值,初始化赋值和变量的定义不能分开,下面的做法是错误的
struct Student stu;
stu = {"MJ", 27};
结构体的使用
- 1.一般对结构体变量的操作是以成员为单位进行的,引用的一般形式为:结构体变量名.成员名
- 2.如果某个成员也是结构体变量,可以连续使用成员运算符”.”访问最低一级成员
- 3.相同类型的结构体变量之间可以进行整体赋值
结构体数组的定义
struct Student {
char *name;
int age;
};
struct Student stu[5]; //定义1
struct Student {
char *name;
int age;
} stu[5]; //定义2
struct {
char *name;
int age;
} stu[5]; //定义3
结构体数组的初始化
struct {
char *name;
int age;
} stu[2] = { {"MJ", 27}, {"JJ", 30} };
//也可以用数组下标访问每一个结构体元素,跟普通数组的用法是一样的
结构体作为函数参数
将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。
struct Student {
......
};
void test(struct Student stu) {
......
......
}
指向结构体的指针
每个结构体变量都有自己的存储空间和地址,因此指针也可以指向结构体变量
结构体指针变量的定义形式:struct 结构体名称 *指针变量名
有了指向结构体的指针,那么就有3种访问结构体成员的方式
结构体变量名.成员名 (*指针变量名).成员名 指针变量名->成员名
#include <stdio.h>
int main(int argc, const char * argv[]) {
// 定义一个结构体类型
struct Student {
char *name;
int age;
};
// 定义一个结构体变量
struct Student stu = {"MJ", 27};
// 定义一个指向结构体的指针变量
struct Student *p;
// 指向结构体变量stu
p = &stu;
/*
这时候可以用3种方式访问结构体的成员
*/
// 方式1:结构体变量名.成员名
printf("name=%s, age = %d \n", stu.name, stu.age);
// 方式2:(*指针变量名).成员名
printf("name=%s, age = %d \n", (*p).name, (*p).age);
// 方式3:指针变量名->成员名
printf("name=%s, age = %d \n", p->name, p->age);
return 0;
}
枚举类型
枚举类型的定义:
- 一般形式为:enum 枚举名 {枚举元素1,枚举元素2,……};
enum Season {spring, summer, autumn, winter};
枚举变量的定义:
//1.先定义枚举类型,再定义枚举变量
enum Season {spring, summer, autumn, winter};
enum Season s;
//2.定义枚举类型的同时定义枚举变量
enum Season {spring, summer, autumn, winter} s;
//3.省略枚举名称,直接定义枚举变量
enum {spring, summer, autumn, winter} s;
枚举使用的注意:
- 1> C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量。
- 2> 枚举元素的值取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。
enum Season {spring, summer, autumn, winter};
//也就是说spring的值为0,summer的值为1,autumn的值为2,winter的值为3
- 3> 也可以在定义枚举类型时改变枚举元素的值
enum season {spring, summer=3, autumn, winter};
//没有指定值的枚举元素,其值为前一元素加1。也就说spring的值为0,summer的值为3,autumn的值为4,winter的值为5
枚举变量的基本操作
- 1.赋值
//可以给枚举变量赋枚举常量或者整型值
enum Season {spring, summer, autumn, winter} s;
s = spring; // 等价于 s = 0;
s = 3; // 等价于 s = winter;
- 2.遍历枚举元素
enum Season {spring, summer, autumn, winter} s;
// 遍历枚举元素
for (s = spring; s <= winter; s++) {
printf("枚举元素:%d \n", s);
}
typedef
typedef作用简介
- 我们可以使用typedef关键字为各种数据类型定义一个新名字(别名)。
typedef int Integer;
typedef unsigned int UInterger;
typedef float Float;
- 也可以在别名的基础上再起一个别名
typedef int Integer;
typedef Integer MyInteger;
typedef与指针
- 除开可以给基本数据类型起别名,typedef也可以给指针起别名
#include <stdio.h>
typedef char *String;
int main(int argc, const char * argv[]) {
// 相当于char *str = "This is a string!";
String str = "This is a string!";
printf("%s", str);
return 0;
}
使用typedef给结构体起别名
// 定义一个结构体
struct MyPoint {
float x;
float y;
};
// 起别名
typedef struct MyPoint Point;
//以上的代码可以简写为
// 定义一个结构体,顺便起别名
typedef struct MyPoint {
float x;
float y;
} Point;
//甚至可以省略结构体名称:
typedef struct {
float x;
float y;
} Point;
int main(int argc, const char * argv[]) {
// 定义结构体变量
Point p;
p.x = 10.0f;
p.y = 20.0f;
return 0;
}
typedef给结构体的指针起别名
#include <stdio.h>
// 定义一个结构体并起别名
typedef struct {
float x;
float y;
} Point;
// 起别名
typedef Point *PP;
int main(int argc, const char * argv[]) {
// 定义结构体变量
Point point = {10, 20};
// 定义指针变量
PP p = &point;
// 利用指针变量访问结构体成员
printf("x=%f,y=%f", p->x, p->y);
return 0;
}
typedef给枚举类型起别名
// 定义枚举类型
enum Season {spring, summer, autumn, winter};
// 给枚举类型起别名
typedef enum Season Season;
int main(int argc, const char * argv[]) {
// 定义枚举变量
Season s = spring;
return 0;
}
//起了别名为Season,然后直接使用别名定义枚举变量,不用再带上enum关键字了
// 定义枚举类型,并且起别名(比上面的代码更加简化)
typedef enum Season {spring, summer, autumn, winter} Season
//甚至可以省略枚举名称,简化为:
typedef enum {spring, summer, autumn, winter} Season;
typedef给指向函数的指针类型起别名
//----------------------------------------------------------------------------------------------------------
//常规函数指针的使用
#include <stdio.h>
// 定义一个sum函数,计算a跟b的和
int sum(int a, int b) {
int c = a + b;
printf("%d + %d = %d", a, b, c);
return c;
}
int main(int argc, const char * argv[]) {
// 定义一个指向sum函数的指针变量p
int (*p)(int, int) = sum;
// 利用指针变量p调用sum函数
(*p)(4, 5);
return 0;
}
//----------------------------------------------------------------------------------------------------------
//typedef给指向函数的指针类型起别名
#include <stdio.h>
// 定义一个sum函数,计算a跟b的和
int sum(int a, int b) {
int c = a + b;
printf("%d + %d = %d", a, b, c);
return c;
}
typedef int (*MySum)(int, int);
int main(int argc, const char * argv[]) {
// 定义一个指向sum函数的指针变量p
MySum p = sum;
// 利用指针变量p调用sum函数
(*p)(4, 5);
return 0;
}
typedef与#define
typedef char *String;
int main(int argc, const char * argv[]) {
String str = "This is a string!";
return 0;
}
-----------------------------------------------------------------------
#define String char *
int main(int argc, const char * argv[]) {
String str = "This is a string!";
return 0;
}
//两段代码效果一样
typedef char *String1;
#define String2 char *
int main(int argc, const char * argv[]) {
String1 str1, str2;
String2 str3, str4;
return 0;
}
//只有str1、str2、str3才是指向char类型的指针变量,str4只是个char类型的变量
//只有str4是基本数据类型,str1、str2、str3都是指针类型。
//所以,以后给类型起别名,最好使用typedef,而不是使用#define