c语言基础(七)---复杂数据类型
文章目录
- 一.结构体
- 1. 定义
- 2. 声明
- 二.联合体
- 1.定义
- 2.赋值
- 3.应用
- 三.枚举
- 1.枚举的由来
- 2. 枚举定义
- 3.枚举变量
- 4.枚举实例测试
- 四.宏
- 1. 宏的理解
- 2. 常量宏定义
- 3. 常量宏定义注意点
- 4. 含参数宏定义
- 5. 含参数宏注意点
- 6.宏参数的拼接
- 7. 含参数宏定义与函数区别
- 五.typedef类型重命名
- 1.基本数据类型
- 2.数组类型
- 3. 结构体类型
- 4.指针类型
- 5. typedef 和 #define 的区别
一.结构体
在c语言中,数据类型分为基本类型(int、double、float、short、long、char)和构造类型(数组,结构体,共用体和枚举)。
我们在构建代码时,往往同种属性的变量并不属于同种基本数据类型,这样使得数组不能够满足我们当前的需求,所以结构体和共用体就应运而生了。今天我们主要讲结构体的应用。
结构体所占内存应该是大于等于成员所占内存总和。这涉及到字节对齐,这方面也是一个比较重要的点,我们在以后会详细介绍。并且各个成员在内存中的储存是连续的。
1. 定义
结构体的成员可以是基本的数据类型,也可以是构造类型,相对来说比较自由。
结构体定义的****一般形式如下所示:
struct 结构体名 {类型名1 成员名1;类型名2 成员名2;...类型名n 成员名n; };
举例:
struct stu{char *name; //姓名int num; //学号int age; //年龄char group; //所在学习小组float score; //成绩 };
stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
注意大括号后面的分号;不能少,这是一条完整的语句。
定义并声明结构体变量:
struct 结构体名 {类型名1 成员名1;类型名2 成员名2;...类型名n 成员名n; }变量1,变量2,...;
将变量放在结构体定义的最后即可。
举例:
struct stu{char *name; //姓名int num; //学号int age; //年龄char group; //所在学习小组float score; //成绩 } stu1, stu2;
如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名,如下所示:
struct{ //没有写 stuchar *name; //姓名int num; //学号int age; //年龄char group; //所在学习小组float score; //成绩 } stu1, stu2;
这样做书写简单,但是因为没有结构体名,后面就没法用该结构体定义新的变量。
定义结构体并赋初值:
struct 结构体名 {类型名1 成员名1;类型名2 成员名2;...类型名n 成员名n; }数组[2]={{a,b,...,n},{a1,b1,...,n1}};
举例:
struct student {char name[10];float score;int num; }stu[2]={{"tom",95.5,4},{"jhon",93,3}};
2. 声明
在声明的时候加上关键字“struct”。
声明的一般形式
struct 结构体名 变量1,变量2;
举例:
struct student stu1,stu2;定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字`struct`不能少。stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。
声明时赋初值**
struct 结构体名 变量1 = {a,b,...,n},变量2;举例: struct student stu1 = {"tom",95.5,4},stu2;
成员的获取和赋值
结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[ ]
获取单个元素,结构体使用点号.
获取单个成员。获取结构体成员的一般格式为:
结构体变量名.成员名;
通过这种方式可以获取成员的值,也可以给成员赋值:
#include <stdio.h>
int main(){struct{char *name; //姓名int num; //学号int age; //年龄char group; //所在小组float score; //成绩} stu1;//给结构体成员赋值stu1.name = "Tom";stu1.num = 12;stu1.age = 18;stu1.group = 'A';stu1.score = 136.5;//读取结构体成员的值printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);return 0;
}
运行结果:
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!
除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如:
struct{char *name; //姓名int num; //学号int age; //年龄char group; //所在小组float score; //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };
不过整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值,这和数组的赋值非常类似。
需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。
二.联合体
1.定义
我们知道结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员。在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union),它的定义格式为:
union 共用体名{成员列表
};
共用体有时也被称为联合或者联合体,这也是 Union 这个单词的本意。
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
2.赋值
共用体也是一种自定义类型,可以通过它来创建变量,例如:
union data{int n;char ch;double f; }; union data a, b, c;
上面是先定义共用体,再创建变量,也可以在定义共用体的同时创建变量:
union data{int n;char ch;double f; } a, b, c;
如果不再定义新的变量,也可以将共用体的名字省略:
union{int n;char ch;double f; } a, b, c;
程序实例
#include <stdio.h>
union data{int n;char ch;short m;
};
int main(){union data a;printf("%d, %d\n", sizeof(a), sizeof(union data) );a.n = 0x40;printf("%X, %c, %hX\n", a.n, a.ch, a.m);a.ch = '9';printf("%X, %c, %hX\n", a.n, a.ch, a.m);a.m = 0x2059;printf("%X, %c, %hX\n", a.n, a.ch, a.m);a.n = 0x3E25AD54;printf("%X, %c, %hX\n", a.n, a.ch, a.m);return 0;
}
运行结果:
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
这段代码不但验证了共用体的长度,还说明共用体成员之间会相互影响,修改一个成员的值会影响其他成员。
要想理解上面的输出结果,弄清成员之间究竟是如何相互影响的,就得了解各个成员在内存中的分布。以上面的 data 为例,各个成员在内存中的分布如下:
成员 n、ch、m 在内存中“对齐”到一头,对 ch 赋值修改的是前一个字节,对 m 赋值修改的是前两个字节,对 n 赋值修改的是全部字节。也就是说,ch、m 会影响到 n 的一部分数据,而 n 会影响到 ch、m 的全部数据。
3.应用
共用体在一般的编程中应用较少,在单片机中应用较多。对于 PC 机,经常使用到的一个实例是: 现有一张关于学生信息和教师信息的表格。学生信息包括姓名、编号、性别、职业、分数,教师的信息包括姓名、编号、性别、职业、教学科目。请看下面的表格:
Name | Num | Sex | Profession | Score / Course |
---|---|---|---|---|
HanXiaoXiao | 501 | f | s | 89.5 |
YanWeiMin | 1011 | m | t | math |
LiuZhenTao | 109 | f | t | English |
ZhaoFeiYan | 982 | m | s | 95.0 |
f 和 m 分别表示女性和男性,s 表示学生,t 表示教师。可以看出,学生和教师所包含的数据是不同的。现在要求把这些信息放在同一个表格中,并设计程序输入人员信息然后输出。
如果把每个人的信息都看作一个结构体变量的话,那么教师和学生的前 4 个成员变量是一样的,第 5 个成员变量可能是 score 或者 course。当第 4 个成员变量的值是 s 的时候,第 5 个成员变量就是 score;当第 4 个成员变量的值是 t 的时候,第 5 个成员变量就是 course。
经过上面的分析,我们可以设计一个包含共用体的结构体,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
#define TOTAL 4 //人员总数
struct{char name[20];int num;char sex;char profession;union{float score;char course[20];} sc;
} bodys[TOTAL];
int main(){int i;//输入人员信息for(i=0; i<TOTAL; i++){printf("Input info: ");scanf("%s %d %c %c", bodys[i].name, &(bodys[i].num), &(bodys[i].sex), &(bodys[i].profession));if(bodys[i].profession == 's'){ //如果是学生scanf("%f", &bodys[i].sc.score);}else{ //如果是老师scanf("%s", bodys[i].sc.course);}fflush(stdin);}//输出人员信息printf("\nName\t\tNum\tSex\tProfession\tScore / Course\n");for(i=0; i<TOTAL; i++){if(bodys[i].profession == 's'){ //如果是学生printf("%s\t%d\t%c\t%c\t\t%f\n", bodys[i].name, bodys[i].num, bodys[i].sex, bodys[i].profession, bodys[i].sc.score);}else{ //如果是老师printf("%s\t%d\t%c\t%c\t\t%s\n", bodys[i].name, bodys[i].num, bodys[i].sex, bodys[i].profession, bodys[i].sc.course);}}return 0;
}
运行结果:
Input info: HanXiaoXiao 501 f s 89.5↙
Input info: YanWeiMin 1011 m t math↙
Input info: LiuZhenTao 109 f t English↙
Input info: ZhaoFeiYan 982 m s 95.0↙Name Num Sex Profession Score / Course
HanXiaoXiao 501 f s 89.500000
YanWeiMin 1011 m t math
LiuZhenTao 109 f t English
ZhaoFeiYan 982 m s 95.000000
三.枚举
1.枚举的由来
在实际编程中,有些数据的取值往往是有限的,只能是非常少量的整数,并且最好为每个值都取一个名字,以方便在后续代码中使用,比如一个星期只有七天,一年只有十二个月,一个班每周有六门课程等。
以每周七天为例,我们可以使用#define
命令来给每天指定一个名字:
#include <stdio.h>
#define Mon 1
#define Tues 2
#define Wed 3
#define Thurs 4
#define Fri 5
#define Sat 6
#define Sun 7
int main(){int day;scanf("%d", &day);switch(day){case Mon: puts("Monday"); break;case Tues: puts("Tuesday"); break;case Wed: puts("Wednesday"); break;case Thurs: puts("Thursday"); break;case Fri: puts("Friday"); break;case Sat: puts("Saturday"); break;case Sun: puts("Sunday"); break;default: puts("Error!");}return 0;
}
运行结果:
5↙
Friday
#define
命令虽然能解决问题,但也带来了不小的副作用,导致宏名过多,代码松散,看起来总有点不舒服。C语言提供了一种枚举(Enum)类型,能够列出所有可能的取值,并给它们取一个名字。
2. 枚举定义
枚举类型的定义形式为:
enum typeName{ valueName1, valueName2, valueName3, ...... };
enum
是一个新的关键字,专门用来定义枚举类型,这也是它在C语言中的唯一用途;typeName
是枚举类型的名字;valueName1, valueName2, valueName3, ......
是每个值对应的名字的列表。注意最后的;
不能少。
例如,列出一个星期有几天:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
可以看到,我们仅仅给出了名字,却没有给出名字对应的值,这是因为枚举值默认从 0 开始,往后逐个加 1(递增);也就是说,week 中的 Mon、Tues … Sun 对应的值分别为 0、1 … 6。
我们也可以给每个名字都指定一个值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
更为简单的方法是只给第一个名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
这样枚举值就从 1 开始递增,跟上面的写法是等效的。
3.枚举变量
枚举是一种类型,通过它可以定义枚举变量:
enum week a, b, c;
有了枚举变量,就可以把列表中的值赋给它:enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a = Mon, b = Wed, c = Sat;或者:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a = Mon, b = Wed, c = Sat;
4.枚举实例测试
程序实例:
#include <stdio.h>
int main(){enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;scanf("%d", &day);switch(day){case Mon: puts("Monday"); break;case Tues: puts("Tuesday"); break;case Wed: puts("Wednesday"); break;case Thurs: puts("Thursday"); break;case Fri: puts("Friday"); break;case Sat: puts("Saturday"); break;case Sun: puts("Sunday"); break;default: puts("Error!");}return 0;
}
-------------------------------------------------------------------------------------------
运行结果:
4↙
Thursday
需要注意的两点是:
1) 枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量。
2) Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。
枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。
对于上面的代码,在编译的某个时刻会变成类似下面的样子:
#include <stdio.h>
int main(){enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;scanf("%d", &day);switch(day){case 1: puts("Monday"); break;case 2: puts("Tuesday"); break;case 3: puts("Wednesday"); break;case 4: puts("Thursday"); break;case 5: puts("Friday"); break;case 6: puts("Saturday"); break;case 7: puts("Sunday"); break;default: puts("Error!");}return 0;
}
Mon、Tues、Wed 这些名字都被替换成了对应的数字。这意味着,Mon、Tues、Wed 等都不是变量,它们不占用数据区(常量区、全局数据区、栈区和堆区)的内存,而是直接被编译到命令里面,放到代码区,所以不能用&
取得它们的地址。这就是枚举的本质。
四.宏
1. 宏的理解
作用:enum给整数命名,typedef给类型命名,我这个宏可以给一切“重命名”(当然本质是替换)。
本质:单纯的替换
define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
2. 常量宏定义
我们先通过一个例子来看一下 #define 的用法:
#include <stdio.h>
#define N 100
int main(){int sum = 20 + N;printf("%d\n", sum);return 0;
}
---------------------------------------------------------------------------------------------
运行结果:
120
注意第 6 行代码int sum = 20 + N
,N
被100
代替了。
#define N 100
就是宏定义,N
为宏名,100
是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。
宏定义是由源程序中的宏定义命令#define
完成的,宏替换是由预处理程序完成的。
宏定义的一般形式为:
#define 宏名 字符串
#
表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名
是标识符的一种,命名规则和变量相同。字符串
可以是数字、表达式、if 语句、函数等。这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串等同,它不需要双引号。
程序中反复使用的表达式就可以使用宏定义,例如:
#define M (n*n+3*n)
它的作用是指定标识符M
来表示(y*y+3*y)
这个表达式。在编写代码时,所有出现 (yy+3y) 的地方都可以用 M 来表示,而对源程序编译时,将先由预处理程序进行宏代替,即用 (yy+3y) 去替换所有的宏名 M,然后再进行编译。
程序实例:
#include <stdio.h>
#define M (n*n+3*n)
int main(){int sum, n;printf("Input a number: ");scanf("%d", &n);sum = 3*M+4*M+5*M;printf("sum=%d\n", sum);return 0;
}
----------------------------------------------------------------------------
运行结果:
Input a number: 10↙
sum=1560
程序开头首先定义了一个宏 M,它表示 (nn+3n) 这个表达式。在 9 行代码中使用了宏 M,预处理程序将它展开为下面的语句:
sum=3*(n*n+3*n)+4*(n*n+3*n)+5*(n*n+3*n);
需要注意的是,在宏定义中表达式(n*n+3*n)
两边的括号不能少,否则在宏展开以后可能会产生歧义。下面是一个反面的例子:
#difine M n*n+3*n
在宏展开后将得到下述语句:
s=3*n*n+3*n+4*n*n+3*n+5*n*n+3*n;
这相当于:
3n2+3n+4n2+3n+5n2+3n
这显然是不正确的。所以进行宏定义时要注意,应该保证在宏替换之后不发生歧义。
3. 常量宏定义注意点
宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用
#undef
命令。例如:#define PI 3.14159 int main(){// Codereturn 0; } #undef PI void func(){// Code }
表示 PI 只在 main() 函数中有效,在 func() 中无效。
代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:
#include <stdio.h> #define OK 100 int main(){printf("OK\n");return 0; } ----------------------------------------------------------------------------------
该例中定义宏名 OK 表示 100,但在 printf 语句中 OK 被引号括起来,因此不作宏替换,而作为字符串处理。
宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。例如:
#define PI 3.1415926 #define S PI*y*y /* PI是已定义的宏名*/
对语句:
printf("%f", S);
在宏代换后变为:
printf("%f", 3.1415926*y*y);
习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
可用宏定义表示数据类型,使书写方便。例如
#define UINT unsigned int
在程序中可用 UINT 作变量说明:
UINT a, b;
应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。
实例:
#define PIN1 int * typedef int *PIN2; //也可以写作typedef int (*PIN2);
从形式上看这两者相似, 但在实际使用中却不相同。
下面用 PIN1,PIN2 说明变量时就可以看出它们的区别:
PIN1 a, b;
在宏代换后变成:
int * a, b;
表示 a 是指向整型的指针变量,而 b 是整型变量。然而:
PIN2 a,b;
表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。在使用时要格外小心,以避出错。
4. 含参数宏定义
C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:
#define 宏名(形参列表) 字符串
在字符串中可以含有各个形参。
带参宏调用的一般形式为:
宏名(实参列表);
实例:
#define M(y) y*y+3*y //宏定义
// TODO:
k=M(5); //宏调用
在宏展开时,用实参 5 去代替形参 y,经预处理程序展开后的语句为k=5*5+3*5
。
【示例】输出两个数中较大的数。
#include <stdio.h>
#define MAX(a,b) (a>b) ? a : b
int main(){int x , y, max;printf("input two numbers: ");scanf("%d %d", &x, &y);max = MAX(x, y);printf("max=%d\n", max);return 0;
}
-----------------------------------------------------------------------
运行结果:
input two numbers: 10 20
max=20
程序第 2 行定义了一个带参数的宏,用宏名MAX
表示条件表达式(a>b) ? a : b
,形参 a、b 均出现在条件表达式中。程序第 7 行max = MAX(x, y)
为宏调用,实参 x、y 将用来代替形参 a、b。宏展开后该语句为:
max=(x>y) ? x : y;
5. 含参数宏注意点
带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。例如把:
#define MAX(a,b) (a>b)?a:b
写为:
#define MAX (a,b) (a>b)?a:b
将被认为是无参宏定义,宏名 MAX 代表字符串
(a,b) (a>b)?a:b
。宏展开时,宏调用语句:max = MAX(x,y);
将变为:
max = (a,b)(a>b)?a:b(x,y);
这显然是错误的。
在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
这一点和函数是不同的:在函数中,形参和实参是两个不同的变量,都有自己的作用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题。
【示例】输入 n,输出 (n+1)^2 的值。
#include <stdio.h> #define SQ(y) (y)*(y) int main(){int a, sq;printf("input a number: ");scanf("%d", &a);sq = SQ(a+1);printf("sq=%d\n", sq);return 0; } ------------------------------------------------------------------------------------- 运行结果: input a number: 9 sq=100
第 2 行为宏定义,形参为 y。第 7 行宏调用中实参为 a+1,是一个表达式,在宏展开时,用 a+1 代换 y,再用 (y)*(y) 代换 SQ,得到如下语句:
sq=(a+1)*(a+1);
这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再传递给形参,而宏展开中对实参表达式不作计算,直接按照原样替换。
在宏定义中,字符串内的形参通常要用括号括起来以避免出错。例如上面的宏定义中 (y)*(y) 表达式的 y 都用括号括起来,因此结果是正确的。如果去掉括号,把程序改为以下形式:
#include <stdio.h> #define SQ(y) y*y int main(){int a, sq;printf("input a number: ");scanf("%d", &a);sq = SQ(a+1);printf("sq=%d\n", sq);return 0; } --------------------------------------------------------------------------------------- 运行结果为: input a number: 9 sq=19
同样输入 9,但结果却是不一样的。问题在哪里呢?这是由于宏展开只是简单的符号替换的过程,没有任何其它的处理。宏替换后将得到以下语句:
sq=a+1*a+1;
由于 a 为 9,故 sq 的值为 19。这显然与题意相违,因此参数两边的括号是不能少的。即使在参数两边加括号还是不够的,请看下面程序:
#include <stdio.h> #define SQ(y) (y)*(y) int main(){int a,sq;printf("input a number: ");scanf("%d", &a);sq = 200 / SQ(a+1);printf("sq=%d\n", sq);return 0; }
与前面的代码相比,只是把宏调用语句改为:
sq = 200/SQ(a+1);
运行程序后,如果仍然输入 9,那么我们希望的结果为 2。但实际情况并非如此:
input a number: 9
sq=200
为什么会得这样的结果呢?分析宏调用语句,在宏展开之后变为:
sq=200/(a+1)*(a+1);
a 为 9 时,由于“/”和“”运算符优先级和结合性相同,所以先计算 200/(9+1),结果为 20,再计算 20(9+1),最后得到 200。
为了得到正确答案,应该在宏定义中的整个字符串外加括号:
#include <stdio.h> #define SQ(y) ((y)*(y)) int main(){int a,sq;printf("input a number: ");scanf("%d", &a);sq = 200 / SQ(a+1);printf("sq=%d\n", sq);return 0; }
由此可见,对于带参宏定义不仅要在参数两侧加括号,还应该在整个字符串外加括号。
6.宏参数的拼接
\ 拼接
#define THREE printf("asd"); \printf ("qwe");
\后面不能有空格,最后一个语句不用加\
#define ONE(x) #x
把传进来的东西都解释成字符串,
printf ("%s", ONE(qweq));
相当于给x的参数加了双引号
字符串拼接
#define ONE(x, y) #x###y printf ("%s\n", ONE(a, qwe));
输出aqwe
7. 含参数宏定义与函数区别
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
【示例①】用函数计算平方值。
#include <stdio.h>
int SQ(int y){return ((y)*(y));
}
int main(){int i=1;while(i<=5){printf("%d^2 = %d\n", (i-1), SQ(i++));}return 0;
}
----------------------------------------------------------------
运行结果:1^2 = 12^2 = 43^2 = 94^2 = 165^2 = 25
【示例②】用宏计算平方值。
#include <stdio.h>
#define SQ(y) ((y)*(y))
int main(){int i=1;while(i<=5){printf("%d^2 = %d\n", i, SQ(i++));}return 0;
}
在 Visual Studio 和 C-Free 下的运行结果(其它编译器的运行结果可能不同,这个++
运算的顺序有关):
3^2 = 1
5^2 = 9
7^2 = 25
在示例①中,先把实参 i 传递给形参 y,然后再自增 1,这样每循环一次 i 的值增加 1,所以最终要循环 5 次。
在示例②中,宏调用只是简单的字符串替换,SQ(i++) 会被替换为 ((i++)*(i++)),这样每循环一次 i 的值增加 2,所以最终只循环 3 次。
由此可见,宏和函数只是在形式上相似,本质上是完全不同的。
带参数的宏也可以用来定义多个语句,在宏调用时,把这些语句又替换到源程序中,请看下面的例子:
#include <stdio.h>
#define SSSV(s1, s2, s3, v) s1 = length * width; s2 = length * height; s3 = width * height; v = width * length * height;
int main(){int length = 3, width = 4, height = 5, sa, sb, sc, vv;SSSV(sa, sb, sc, vv);printf("sa=%d, sb=%d, sc=%d, vv=%d\n", sa, sb, sc, vv);return 0;
}
-------------------------------------------------------------------------------
运行结果:sa=12, sb=15, sc=20, vv=60
五.typedef类型重命名
1.基本数据类型
C语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。
起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如有一个结构体的名字是 stu,要想定义一个结构体变量就得这样写:
struct stu stu1;
struct 看起来就是多余的,但不写又会报错。如果为 struct stu 起了一个别名 STU,书写起来就简单了:
STU stu1;
这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,都会大量使用这种别名。
使用关键字 typedef 可以为类型起一个新的别名。typedef 的用法一般为:
typedef oldName newName;
oldName 是类型原来的名字,newName 是类型新的名字。例如:
typedef int INTEGER;
INTEGER a, b;
a = 1;
b = 2;
INTEGER a, b;
等效于int a, b;
。
2.数组类型
typedef char ARRAY20[20];
表示 ARRAY20 是类型char [20]
的别名。它是一个长度为 20 的数组类型。接着可以用 ARRAY20 定义数组:
ARRAY20 a1, a2, s1, s2;
它等价于:
char a1[20], a2[20], s1[20], s2[20];
注意,数组也是有类型的。例如char a1[20];
定义了一个数组 a1,它的类型就是 char [20],
3. 结构体类型
typedef struct stu{char name[20];int age;char sex;
} STU;STU 是 struct stu 的别名,可以用 STU 定义结构体变量:
STU body1,body2;
它等价于:
struct stu body1, body2;
4.指针类型
typedef int (*PTR_TO_ARR)[4];
表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:PTR_TO_ARR p1, p2;
按照类似的写法,还可以为函数指针类型定义别名:
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
程序实例:
#include <stdio.h>
typedef char (*PTR_TO_ARR)[30];
typedef int (*PTR_TO_FUNC)(int, int);
int max(int a, int b){return a>b ? a : b;
}
char str[3][30] = {"http://c.biancheng.net","C语言中文网","C-Language"
};
int main(){PTR_TO_ARR parr = str;PTR_TO_FUNC pfunc = max;int i;printf("max: %d\n", (*pfunc)(10, 20));for(i=0; i<3; i++){printf("str[%d]: %s\n", i, *(parr+i));}return 0;
}
----------------------------------------------------------------------------------
运行结果:
max: 20
str[0]: http://c.biancheng.net
str[1]: C语言中文网
str[2]: C-Language
需要强调的是,typedef 是赋予现有类型一个新的名字,而不是创建新的类型。为了“见名知意”,请尽量使用含义明确的标识符,并且尽量大写。
5. typedef 和 #define 的区别
typedef 在表现上有时候类似于 #define,但它和宏替换之间存在一个关键性的区别。正确思考这个问题的方法就是把 typedef 看成一种彻底的“封装”类型,声明之后不能再往里面增加别的东西。
- 可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。如下所示:
#define INTERGE int
unsigned INTERGE n; //没问题typedef int INTERGE;
unsigned INTERGE n; //错误,不能在 INTERGE 前面添加 unsigned
- 在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
#define PTR_INT int *
PTR_INT p1, p2;经过宏替换以后,第二行变为:
int *p1, p2;这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。
相反,在下面的代码中
typedef int * PTR_INT
PTR_INT p1, p2;p1、p2 类型相同,它们都是指向 int 类型的指针。
c语言基础(七)---复杂数据类型相关推荐
- Go语言基础之基本数据类型
直接上代码 package mainimport ("fmt""math" )func main() {// 整型fmt.Println("===整型 ...
- c语言无视数据类型字符串存储,C语言基础-第二课-数据类型与运算符(示例代码)...
1 C语言中的数据类型 1.1 常量 常量就是在程序中不可变化的量,常量在定义的时候必须给一个初值. 1.1.1#define 定义一个宏常量 1.1.2const 定义一个const常量 1 ...
- c语言基本数据类型常量,C语言基础学习基本数据类型-变量和常量
变量和常量 什么是变量和常量?有些数据在程序运行前就预先设定,并在运行过程中不发生变化,称之为常量:有些数据在程序运行中可能发生变化或被赋值,称之为变量. 使用变量前必须先声明一个变量.变量定义的语法 ...
- C语言怎么定义001为int,C语言基础-01-基本数据类型
C语言的数据类型大致可以分为下图中的几类: 一.变量 跟其他语言一样,C语言中用变量来存储计算过程使用的值,任何变量都必须先定义类型再使用.为什么一定要先定义呢?因为变量的类型决定了变量占用的存储空间 ...
- JSD-2204-Java语言基础-八大基本数据类型-Day02
1.变量:存数的 声明:---------在银行开个账户 int a; //声明一个整型的变量,名为a int b,c,d; //声明三个整型的变量,名为b,c,d //int a; //编译错误,变 ...
- go语言基础语法-容器数据类型-数组
文章目录 前言 一. 数组 1. 数组的语法和声明 2 语法 1)语法: 2. 数组的初始化 a) 先声明再赋值 (先声明在初始化) b) 声明并初始化 c) 不限长赋值 d) 索引赋值 3. 访问和 ...
- C语言基础1(数据类型、常变量、运算符、基本语句、选择结构、循环结构、数组、字符串、函数、指针)
数据类型及运算 数据类型 一.整形(int) 整形变量的定义.输入和输出 打印格式 含义 %d 输出一个有符号的10进制int类型 %o(字母o) 输出8进制的int类型 %x 输出16进制的int类 ...
- C语言基础知识【数据类型】
C 数据类型 1.在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统.变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式. 2.C 中的类型可分为以下几种: 序号 ...
- c语言变量类型int,C语言基础学习基本数据类型-int类型与int变量
C++学习笔记26:泛型编程概念 一.什么是泛型编程? 泛型就是通用的型式 编写不依赖数据对象型式的代码就是泛型编程 二.为什么需要泛型编程? 函数重载,相似类定义与型式兼容性 例如:设计函数,求两个 ...
- c语言中shift f12组合建,C++学习1-(C语言基础、VS快捷键)
C语言基础复习 1.三码 正数: 3码合1 ,正数的反码/补码就是其本身 负数: 原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值 原码:11010101 负数的反码是在其原码的基础上 ...
最新文章
- 通用社区登陆组件技术分享(开源)中篇:OAuth 登陆组件流程及组件集成方法...
- mac版lightroom cc_Photoshop问世30周年 Mac和iPad版获重要更新
- 微软向Linux社区开放60000多项专利:对开源微软是认真的
- ChEMBL数据库的官方python工具包
- python怎么定时弹窗_PyQt5弹框定时关闭(python)
- 单片机定时器之改良版:时间轮定时器
- 亏损208亿,滴滴橙心优选裁员关停!曾融资12亿,最高估值320亿
- ubuntu18.04新体验
- 819 c语言程序设计,大连海洋大学2021年考研819高级语言程序设计(C语言)考试大纲...
- ios python3.0编程软件_ios编程软件-7款学习Python编程的iPhone/iPad应用
- oracle清理磁盘空间
- python图片压缩算法_Python实现奇异值分解(SVD)压缩图片
- NUC10 i7 黑苹果Big Sur 11.4 + win10 双系统安装指南
- 华为防火墙简介及其工作原理
- mysql隔离级别 isolation_MySQL事务和隔离级别
- 国瀚实业|怎么才能做好互联网投资理财
- 深蓝卡通风人教版小学五年级英语课件PPT模板
- PC机插入麦克风后没有说话没有声音
- mysql mooc_爬取MOOC课程(1)
- 太阳直射点纬度计算公式_高中地理——每日讲1题(太阳高度角、太阳直射点、昼夜长短)...
热门文章
- computer management.lnk未指定的错误
- 微信论坛交流小程序系统毕业设计毕设(1)开发概要
- 使用调制解调器的基本 AT 命令
- Go json.Marshal禁用escapeHtml转义
- 孕妇的孕周和体重对胎儿游离DNA在母体cfDNA占比的影响
- 家园系统服务器,梦幻西游手游家园系统介绍 家园外观全面升级-游侠手游
- 死锁的四个必要条件及处理死锁
- opencv图片倾斜度检测(二)利用摄像头进行实时检测图片中物体并画出坐标轴和倾斜度
- Day 7 A - Age of Moyu HDU 6386 | 最短路 | SPFA | 链式前向星
- javaweb项目创建图片服务器