目录
十、结构体和共用体
1、struct结构体的定义和使用
2、结构体数组的使用
3、结构体指针及具体操作
4、union共用体的定义和使用
5、typedef用法
六、enum枚举类型
练习一:星期判断机
练习二:自定义函数之字符串拷贝
练习三:结构体之成绩统计
十一、文件操作
1、文件读写
2、打开文件fopen函数
3、写文件fprintf函数
4、读文件fscanf函数
5、写文件fwrite函数
6、读文件fread函数
7、关闭文件fclose函数
十二、C语言预处理
1、宏定义define
(1)无参数宏
(2)有参数宏
2、文件包含include
3、条件编译
4、其他预处理命令
(1)#error
(2)#line
(3)#pragma
练习一:带参数宏定义练习
练习二:宏定义练习之三角形面积
练习三:宏定义之找最大数
练习四:宏定义之闰年判断
十三、位运算
1、移位运算
2、&按位与运算
3、|按位或运算
4、^按位异或运算
5、~取反运算
附录
ASC码表大全
十、结构体和共用体
这一章主要介绍C语言struct结构体的定义和使用、C语言结构体数组的使用、结构体指针及具体操作、union共用体的定义和使用、C语言typedef用法以及C语言enum枚举类型。
结构体(struct)是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。【之前的数组都是存放相同类型的元素】。
共用体(union),也称为联合体,是用于在不同时刻保存不同类型和长度的变量,它提供了一种方式,以在单块存储区中管理不回类型的数据。
1、struct结构体的定义和使用
字符指针也可以指向一个字符串,可以用字符串常量对字符指针进行初始化。例如:
char *str = "www.dotcpp.com" ;
这是对字符指针进行初始化。此时,字符指针指向一个字符串常量的首地址。
结构体也是由若干分量组成,但是其中成员可以是不同类型的,可以通过成员名来访问结构体的元素。
结构体的定义说明了它的组成成员,以及每个成员的数据类型。定义一般形式如下:
struct
结构类型名
{
数据类型 成员名 1;
数据类型 成员名 2;
......
数据类型 成员名 n;
};
如果要使用该结构,就必须说明结构类型的标量。结构变量说明一般是:
struct 结构类型名称 结构变量名;
系统会按照结构定义时的内部组成,为说明的结构变量分配内存空间。结构变量的成员在内存中占用连续的存储区域,所占内存大小为结构中每个成员的长度之和。
将变量student1说明为address类型的结构变量:
struct address student1;
如果要访问结构中的某个成员:
结构变量名.成员名称
如student1.tel表示结构变量student1的电话信息。
结构初始化的一般形式如下:
struct 结构类型名 结构变量 = { 初始化数据 1, ...... 初始化数据 n };
结构体定义完以后要有一个分号,不然会报错。
#include<stdio.h>
#include<string.h>
struct _INFO{int num;char str[256];
};int main(void){struct _INFO A;A.num=100;strcpy(A.str,"HELLO WORLD!");printf("%d %s",A.num,A.str);return 0;
}
输出:100 HELLO WORLD!
2、结构体数组的使用
结构体数组是一个数组,其数组的每一个元素都是结构体类型。在实际应用中,经常用结构体数组来表示具有相同数据结构的一个群体,如一个班的学生档案等。
定义结构体数组和结构体变量相仿,只需说明它为数组类型即可。比如定义一个结构体数组student,包含3个元素:student[0]、student[1]、student[2],每个数组元素都具有struct address的结构形式,并对该结构体数组进行初始化赋值。
struct address
{char name[30]; /*姓名,字符数组作为结构体中的成员*/char street[40]; /*街道*/unsigned long tel; /*电话,无符号长整型作为结构体中的成员*/unsigned long zip; /*邮政编码*/
}student[3]={{"Zhang","Road NO.1",111111,4444},{"Wang"," Road NO.2",222222,5555},{"Li"," Road NO.3",333333,6666}
};
逐个输出结构体中的数据:
#include<stdio.h>
#include<string.h>
struct address
{char name[30]; /*姓名,字符数组作为结构体中的成员*/char street[40]; /*街道*/unsigned long tel; /*电话,无符号长整型作为结构体中的成员*/unsigned long zip; /*邮政编码*/
}student[3]={{"Zhang","Road NO.1",111111,4444},{"Wang"," Road NO.2",222222,5555},{"Li"," Road NO.3",333333,6666}
};int main(void) {int i;for (i = 0; i < 3; i++) {printf("Name: %s\n", student[i].name);printf("Street: %s\n", student[i].street);printf("Tel: %lu\n", student[i].tel);printf("Zip: %lu\n", student[i].zip);printf("\n");}return 0;
}
结果:
3、结构体指针及具体操作
结构体指针即指向结构体的指针。
当一个指针用来指向一个结构体变量时,称之为结构体指针变量。结构体指针变量中的值是所指向的结构变量的首地址,通过结构指针即可访问该结构变量。
struct 结构类型名 *结构指针变量名
定义后,可以通过指针间接访问结构体。
不用于结构体变量用点(.)来访问成员的方法,结构体指针是通过箭头(->)来访问。
#include<stdio.h>
#include<string.h>
struct address
{char name[30]; /*姓名,字符数组作为结构体中的成员*/char street[40]; /*街道*/unsigned long tel; /*电话,无符号长整型作为结构体中的成员*/unsigned long zip; /*邮政编码*/
};int main(void) {struct address stu[3]={{"Zhang","Road NO.1",111111,4444},{"Wang"," Road NO.2",222222,5555},{"Li"," Road NO.3",333333,6666}};struct address *p;p=&stu[0];printf("%s %s %u %u\n",p->name,p->street,p->tel,p->zip);return 0;
}
使用循环输出所有结构体元素:
#include<stdio.h>
#include<string.h>
struct address
{char name[30]; /*姓名,字符数组作为结构体中的成员*/char street[40]; /*街道*/unsigned long tel; /*电话,无符号长整型作为结构体中的成员*/unsigned long zip; /*邮政编码*/
};int main(void) {struct address stu[3]={{"Zhang","Road NO.1",111111,4444},{"Wang"," Road NO.2",222222,5555},{"Li"," Road NO.3",333333,6666}};struct address *p;p=&stu[0];for(int i=0;i<3;i++){printf("%s %s %u %u\n",p->name,p->street,p->tel,p->zip);p++;}return 0;
}
4、union共用体的定义和使用
允许几种不同类型的变量存放到同一段内存单元中,也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,被称为共用体类型结构,简称共用体。一般定义形式为:
union 共用体名
{ 数据类型 成员名 1; 数据类型 成员名 2; ...... 数据类型 成员名 n;
}变量名表列;
只有先定义了共用体变量,才能在后续的程序中引用它。不能直接引用共用体变量,而只能引用共用体变量中的成员。引用方法如下:
共用体变量名.成员名
共用体类型数据具有以下特点:
(1)同一个内存段可以用来存放几种不同类型的成员,但是在每一瞬间只能存放其中的一种,而不是同时存放几种。换句话说,每一瞬间只有一个成员起作用,其他的成员不起作用,即不是同时都存在和起作用的。
(2)共用体变量中起作用的成员是最后一次存放的成员,在存入一个新成员后,原有成员就失去作用。共用体变量的地址和它的各成员的地址都是同一地址。
#include<stdio.h>
#include<string.h>
union A{int a,b,c;
};
int main(){union A B;B.a=1;B.b=2;B.c=3;printf("%d %d %d",B.a,B.b,B.c);return 0;
}
共同体有几个要点:
(1)不能对共同体变量名赋值,也不能引用变量名得到值;
(2)不能在定义共同体变量时对它进行初始化。
(3)不能把共用体变量作为函数参数,也不能是函数返回共用体变量,但可以使用指向共用体变量的指针。
(4)共用体类型可以出现在结构体类型的定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型的定义中,数组也可以作为共用体的成员。
5、typedef用法
可以使用类型说明语句typedef定义新的类型来代替已有的类型。typedef语句的一般形式是:
typedef 已定义的类型 新的类型;
例如(类似于换了个名字?):
typedef int INTEGER; /*指定用 INTEGER 代表 int 类型*/
typedef float REAL; /*指定用 REAL 代表 float 类型*/
在具有上述typedef语句的程序中,下列语句就是等价的。
int i, j; /*与 INTEGER i,j;*/
float pi; /*与 REAL pi;*/
例如(1):
int main(){typedef int INT;INT i=2;printf("%d",i);
}
typedef的最常用的作用就是给结构体变量重命名。
#include<stdio.h>
#include<string.h>
typedef struct _INFO{int num;char str[100];
}INFO;int main(){struct _INFO A;INFO B; //等价于上一句,但不能再写struct了A.num=1;strcpy(A.str,"this is first!");B.num=2;strcpy(B.str,"THIS IS SECOND");printf("%d %s",A.num,A.str);printf("%d %s",B.num,B.str);
}
从上述示例中可以看到typedef可以为关键词改名,使改名之后的INFO类型等价于struct _INFO类型。这样可以更方便定义这种结构类型(但是在使用时就不能再使用struct INFO B了)。
六、enum枚举类型
枚举类型,可以让代码更简介、更易读,通过关键字enum实现,一般形式如下:
enum 枚举名 {枚举元素1,枚举元素2,……};
这样就会定义了一个“枚举名”的枚举类型,其值为枚举元素1(即0)、枚举元素2(即1)...(依次递增)。
#include<stdio.h>
#include<string.h>
int main(){enum week{MON, TUE, WED, THU, FRI, SAT, SUN};enum week A=WED;printf("%d",A);return 0;
}
输出:2
是因为枚举enum默认枚举元素从0开始,如果需要修改其中枚举的类型,则可以修改其中的某个值,后续的所有元素就会在这个基础上递增。
#include<stdio.h>
#include<string.h>
int main(){enum week{MON, TUE=6, WED, THU, FRI, SAT, SUN};enum week A=WED;printf("%d",A);return 0;
}
输出:7
练习一:星期判断机
要读题,要求是0为星期日,但是惯性写成星期一了。
#include<stdio.h>
#include<string.h>
int main(){int n;scanf("%d",&n);switch(n){case 0:printf("Sunday");break;case 1:printf("Monday");break;case 2:printf("Tuesday");break;case 3:printf("Wednesday");break;case 4:printf("Thursday");break;case 5:printf("Friday");break;case 6:printf("Saturday");break;default:printf("input error!");break;}
}
练习二:自定义函数之字符串拷贝
有一字符串,包含n个字符。写一函数,将此字符串中从第m个字符开始的全部字符复制成为另一个字符串。
#include<stdio.h>
#include<string.h>
int main(){int n,m;scanf("%d",&n);char arry[100];scanf("%s",arry);scanf("%d",&m);for(int i=m-1;i<n;i++){printf("%c",arry[i]);}return 0;
}
其实就是一个for循环,从m-1的位置开始读就行。
练习三:结构体之成绩统计
有N个学生,每个学生的数据包括学号、姓名、3门课的成绩,从键盘输入N个学生的数据,要求打印出3门课的总平均成绩,以及最高分的学生的数据(包括学号、姓名、3门课成绩)
思路:学号用char类型,score可以用一个3元素的一维数组表示。
#include<stdio.h>
#include<string.h>
struct student{char id[100],name[100];int score[3];
};struct student stu[100];//定义输入函数
void input(int n){for(int i=0;i<n;i++){scanf("%s %s %d %d %d",&stu[i].id,&stu[i].name,&stu[i].score[0],&stu[i].score[1],&stu[i].score[2]);}
}
//定义平均数函数
void aver(int n){int s1=0,s2=0,s3=0;for(int i=0;i<n;i++){s1+=stu[i].score[0];s2+=stu[i].score[1];s3+=stu[i].score[2];}printf("%d %d %d\n",s1/n,s2/n,s3/n);
}
//定义求最高分函数
void max(int n){int max=stu[0].score[0]+stu[0].score[1]+stu[0].score[2];int t=0;for(int i=1;i<n;i++){if(stu[i].score[0]+stu[i].score[1]+stu[i].score[2]>max){max=stu[i].score[0]+stu[i].score[1]+stu[i].score[2];t=i;}}if(t==0){printf("%s %s %d %d %d\n",stu[0].id,stu[0].name,stu[0].score[0],stu[0].score[1],stu[0].score[2]);}else{printf("%s %s %d %d %d\n",stu[t].id,stu[t].name,stu[t].score[0],stu[t].score[1],stu[t].score[2]);}
}int main(void){int n;scanf("%d",&n);input(n);aver(n);max(n);return 0;
}
刚开始提交错误是应该在for循环完成后才能printf输出相应的最大分数的学生信息结果。
十一、文件操作
这一章主要介绍C语言实现文件读写、打开文件fopen函数的用法、写文件fprintf函数的用法、读文件fscanf函数的用法、写文件fwrite函数的用法、读文件fread函数的用法以及关闭文件fclose函数的用法。
1、文件读写
对于文件的操作分为三个步骤:
第一步:打开文件
第二步:读写文件
第三步:关闭文件
每一步的操作都有对应的函数来提供“接口”来帮助实现。
(1)打开文件:可以使用fopen函数实现,此时主要是建立程序和文件的关系,获取文件在内存中的文件指针,方便后续两步。
(2)读写文件分为fprintf、fscanf或者fwrite、fread或者fputs、getss等多组函数来实现。
(3)关闭文件则需要fclose函数实现。断开文件指针和文件的关联,避免误操作。
2、打开文件fopen函数
作用是打开文件,获取该文件的文件指针,方便后续操作。函数原型为:
FILE *fopen(const char *filename, const char *mode);
该函数需要两个字符串类型的参数,第一个是文件名,既要操作的文件对象。第二个是打开方式,这里的打开方式只是,对文件以何种模式打开,包括文本模式打开还是二进制打开、是读还是写还是追加等等等等,具体类型如下表,可以根据情况使用:
参数 | 作用 |
r | 以只读方式打开文件,该文件必须存在。 |
r+ | 以读/写方式打开文件,该文件必须存在。 |
rb+ | 以读/写方式打开一个二进制文件,只允许读/写数据。 |
rt+ | 以读/写方式打开一个文本文件,允许读和写。 |
w | 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
w+ | 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
a | 以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。 |
a+ | 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。 |
wb | 以只写方式打开或新建一个二进制文件,只允许写数据。 |
wb+ | 以读/写方式打开或新建一个二进制文件,允许读和写。 |
wt+ | 以读/写方式打开或新建一个文本文件,允许读和写。 |
at+ | 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 |
ab+ | 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。 |
函数的返回值则表示打开成功后的文件指针,格式为FILE类型,是一个结构体类型,供后面使用,如果打开失败,则返回NULL。
FILE结构体的定义:
typedef struct { int level; /* fill/empty level of buffer */ unsigned flags; /* File status flags */ char fd; /* File descriptor */ unsigned char hold; /* Ungetc char if no buffer */ int bsize; /* Buffer size */ unsigned char _FAR *buffer; /* Data transfer buffer */ unsigned char _FAR *curp; /* Current active pointer */ unsigned istemp; /* Temporary file indicator */ short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
例如,想打开一个D盘根目录下的abc.dat,并且想读出该文件里的数据:
FILE *fp;
fp=fopen("d:\\abc.dat","r")
//后面通过fp指针开始读文件
注意:
(1)不写盘符表示相对路径;
(2)单个反斜杠“\”当做转义字符使用,所以绝对路径要写两个“\”。
3、写文件fprintf函数
如果打开模式是写,那么可以用fprintf函数来进行写。
int fprintf (FILE* stream, const char*format, [argument])
该函数是一个格式化写入的库函数,和printf函数相比,多了一个第一个参数文件指针,即第一步打开文件时得到的文件指针,后面的参数和printf一样,按照指定的格式将数据写入文件。例如:
fprintf(fp,"%s","hello");
这句代码的意思是将字符串"hello"以%s的格式写入fp所指向的文件中。
控制符 | 作用 |
%c | 单个字符 |
%d 或 %i | 有符号十进制整数 |
%e | 使用 e 字符的科学科学记数法(尾数和指数) |
%E | 使用 E 字符的科学科学记数法(尾数和指数) |
%f | 十进制浮点数 |
%g | 自动选择 %e 或 %f 中合适的表示法 |
%G | 自动选择 %E 或 %f 中合适的表示法 |
%o | 有符号八进制 |
%s | 字符的字符串 |
%u | 无符号十进制整数 |
%x | 无符号十六进制整数 |
%X | 无符号十六进制整数(大写字母) |
%p | 指针地址 |
%n | 无输出 |
% | 字符 |
返回值为整型,如果写入成功则返回写入字符的格式,否则返回一个负数。
4、读文件fscanf函数
fprintf负责向文件里写数据,fscanf函数则可以从文件里读数据,它的函数原型如下:
int fscanf(FILE *stream, char *format[,argument...]);
格式写数据函数,相比scanf多一个参数(即文件指针),表示读取的文件目标,按照相应格式进行读取,返回值表示读取数据的字节数。
char str[100];
fscanf(fp,"%s",str);
表示从fp所指向的文件中进行读数据,与空格或换行结束,将结果保存到str数组中。
格式 | 作用 |
%d | 读入一个十进制整数 |
%i | 读入十进制,八进制,十六进制整数,与%d类似,但是在编译时通过数据前置或后置来区分进制,如加入“0x”则是十六进制,加入“0”则为八进制。例如串“031”使用%d时会被算作31,但是使用%i时会算作25 |
%u | 读入一个无符号十进制整数 |
%f %F %g %G | 用来输入实数,可以用小数形式或指数形式输入 |
%x %x | 读入十六进制整数 |
%o | 读入八进制整数 |
%s | 直到遇到一个空格字符(空格字符可以是空白、换行和制表符) |
%c | 单个字符:读取下一个字符。如果指定了一个不为 1 的宽度 width,函数会读取 width 个字符,并通过参数传递,把它们存储在数组中连续位置。在末尾不会追加空字符 |
5、写文件fwrite函数
函数原型如下:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
函数的参数有四个:
- ptr是要写入的数据的头指针,无符号类型;
- 参数size是大小,表示每个写入元素的大小,单位是字节;
- 参数nmemb是个数,以上一个参数为单位的个数;
- 参数stream就是文件指针,表示往哪里写。
至于返回值,如果成功执行,则返回写入元素的个数,如果不和nmemb相等,则表示出错。
#include<stdio.h>int main ()
{FILE *fp; char str[] = "hello world!"; fp = fopen( "1.dat" , "w" ); fwrite(str, sizeof(str) , 1, fp ); fclose(fp); return(0);
}
程序运行后,并不会在屏幕上有任何显示。运行结束后,看1.dat文件中会被写入str中的字符串。
6、读文件fread函数
函数原型如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
参数意思是:
- 参数ptr表示盛放内容的首地址;
- 参数size表示每个元素的大小,单位还是字节;
- 参数nmem表示要读取的元素个数;
- 参数stream表示的是文件指针,即从哪个文件中读取。
返回值则是表示读取元素的个数,与nmemb一致表示读取成功,否则失败。
#include <stdio.h>
#include <string.h>
int main()
{FILE *fp;char buffer[100];/* 首先打开文件,读写都可以,假设文件中已经有内容为hello world! */fp = fopen("1.dat", "w+");/* 读取并显示数据 */fread(buffer, 1, 15, fp);printf("%s\n", buffer);fclose(fp);return(0);
}
注意文件的打开格式,保证文件中是否有数据,同时buffer空间足够大。
7、关闭文件fclose函数
断开程序与文件关联,切断IO数据流,释放文件不再占用,结束文件。fclose函数原型:
int fclose( FILE *fp );
其中fp为第一步fopen时成功打开文件后的文件指针;返回值为整型,如成功关闭则返回0,失败则返回-1。
如果不执行关闭的话,可能前面的读写都不会成功。
十二、C语言预处理
主要介绍C语言宏定义define的用法、C语言文件包含include的用法、C语言条件编译的用法以及其他预处理命令。
预处理命令可以改变程序设计环境,提高编程效率,它们并不是C语言本身的组成部分,不能直接对它们进行编译,必须在对程序进行编译之前,先对程序中这些特殊的命令进行“预处理”。经过预处理后,程序就不再包括预处理命令了,最后再由编译程序对预处理之后的源程序进行编译处理,得到可供执行的目标代码。
C语言提供的预处理功能有三种,分别为宏定义、文件包含和条件编译。
1、宏定义define
宏定义在C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”,被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的,宏代换是由预处理程序自动完成的。在C语言中,宏分为有参数和无参数两种。
(1)无参数宏
无参宏的宏名后不带参数,其定义的一般形式为:
#define 标识符(所定义的宏名) 字符串;
“#”表示这是一条预处理命令(在C语言中凡是以“#”开头的均为预处理命令)“define”为宏定义命令,“标识符”为所定义的宏名,“字符串”可以是常数、表达式、格式串等。符号常量的定义就是一种无参宏定义。
例如,对程序中反复使用的表达式进行宏定义:
#define M (y*y+3*y); //指定标识符M代替表达式(y*y+3*y)
在编写源程序时,所有的(y*y+3*y)都可由M代替,而对源程序进行编译时,将先由预处理程序进行宏代换,即用(y*y+3*y)表达式去置换所有的宏名M,然后再进行编译。
(2)有参数宏
① 定义形式:
#define 宏名(形参表) 字符串;
字符串中含有各个形参;
② 调用形式:
宏名(实参表);
例如:
#define M(y) y*y+3*y
/*宏定义*/
......
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;scanf("%d %d",&x,&y);max=max(x,y);printf("max is %d",max);return 0;
}
输入:1 2
输出:max is 2
2、文件包含include
文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。
在程序设计中,文件包含是很有用的。一个大的程序可以分为多个模块,由多个程序员分别编程,有些公用的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。
(1)包含命令下述两种都可以,但是存在部分区别:
#include "文件名"
或者
#include <文件名>
- 使用尖括号表示在包含文件目录中去查找(包含目录是由系统的环境变量进行设置的,一般为系统头文件的默认存放目录,比如Linux系统在/usr/include目录下),而不在源文件的存放目录中查找;
- 使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。
(2)一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。
(3)文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
3、条件编译
预处理程序提供了条件编译的功能,可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件,这对于程序的移植和调试是很有用的。条件编译可分为三种形式。
(1)如果标识符已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
(2)如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
和(1)中功能刚好相反。
(3)如果常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。
#if 常量表达式
程序段 1
#else
程序段 2
#endif
4、其他预处理命令
(1)#error
#error指令强制编译程序停止编译,它主要用于程序调试。#error指令的一般形式是:
#error error-message
注意:宏串error-message不用双引号引起来。遇到#error指令时,错误信息被显示,可能同时还显示编译程序作者预先定义的其他内容。
(2)#line
#line指令改变__LINE__和__FILE__的内容。__LINE__和__FILE__都是编译程序中预定义的标识符。__FILE__的内容是当前被编译源文件的文件名。
#line标识符__LINE__的内容是当前被编译代码行的行号,
#line number "filename"
其中,number是正整数并变成__LINE__的新值;可选的“filename”是合法文件标识符并变成__FILE__的新值。#line主要用于调试和特殊应用。
(3)#pragma
#pragma是编译程序实现时定义的指令,它允许由此向编译程序传入各种指令。例如:一个编译程序可能具有支持跟踪程序执行的选项,此时可以用#pragma语句选择该功能,编译程序忽略其不支持的#pragma选项。使用#pragma预处理命令可提高C源程序对编译程序的可移植性。
练习一:带参数宏定义练习
本章总结与作业 - C语言教程 - C语言网
练习二:宏定义练习之三角形面积
练习三:宏定义之找最大数
练习四:宏定义之闰年判断
十三、位运算
位运算以数值的二进制位为单位进行操作的,包含<<(左移)、>>(右移)、~(按位取反)、&(按位与)、|(按位或)、^(按位异或)共六种运算符。
左移运算符<< | 单目 | 向左(即高位)移位,右侧补0 |
右移运算符>> | 单目 | 向右(即低位)移位,左侧补0 |
按位取反~ | 单目 | 如名,即0变1,1变0 |
按位与& | 双目 | 相对应的两个位都为1则为1,反之为0 |
按位或| | 双目 | 相对应的两个位至少有一个为1即为1,反之为0 |
按位异或^ | 双目 | 相对应的两个位相同为0,相异(不同)为1 |
1、移位运算
<<和>>运算符,是位运算符中的左移运算符和右移运算符。
如表达式13<<2,它的运算过程为:13的用二进制(四个字节,不考虑符号)表示为,0000 0000 0000 0000 0000 0000 0000 1101,那么向左移两位,右侧补0,则变为0000 0000 0000 0000 0000 0000 0011 0100 换成十进制即变为52。
移位运算符的作用:
1. 左移N位的本质是乘以2的N次方。
2. 右移N位的本质是除以2的N次方。
2、&按位与运算
和逻辑与(&&)运算符不同的是,按位与是使用二进制进行比较,比如5和7二进制分别是111和101,程序:
#include<stdio.h>
int main()
{int a;a=7&5;printf("a=%d\n",a);return 0;
}
输出的结果就是:a=5
按位与运算符的作用:
1. 清零:
可以对某一个数与0进行按位与运算,由于两个位都为1才为1,因此最终全部位都变为0,起到清零的作用。
2. 取指定位:
如某些存储场景下,“第1~3位表示xxxx“”,需要取出1~3位,则可以让原数值与数字7进行按位与运算,得到的结果即是原数值的1~3位的值。
3. 判断奇偶:
数字的奇偶取决于二进制位的最低一位是1还是0,因此只需要与1按位与运算,判断是1是0即可得知奇偶。
3、|按位或运算
和逻辑或运算符不同的是,只有一个竖线表示,作用位,两个对应的二进制位有一个为1结果即为1。
#include<stdio.h>
int main()
{int a;a=7|5;printf("a=%d\n",a);return 0;
}
输出:7
按位或运算符的作用:
对一个数字的指定位置为1,如“某个数字的第七位”表示开关,原先是0,需要改为1的状态,即可以将这个数字与64按位或,即可得到第七位变为1,其余位的值依旧不变。
4、^按位异或运算
不同则为1,反之为0。
#include<stdio.h>
int main()
{int a;a=7^5;printf("a=%d\n",a);return 0;
}
输出:2
异或运算符的作用:
指定位数的翻转,就可以使用异或来完成。
5、~取反运算
单目运算,对数值的二进制位进行取反。
#include<stdio.h>
int main()
{int a=8;printf("~a=%d\n",~a);printf("~a=%u\n",~a);return 0;
}
附录
ASC码表大全
C语言的暂时到此为止,下面就要接着看C++的了。
道阻且长,浮浮沉沉沉沉沉沉沉沉沉沉……