提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
此文章为C语言基础,包括 数据类型;运算符,表达式和语句;循环;
分支和跳转;函数;数组;结构体与联合体(重要);指针 (重要);宏定义
声明整形
int test1;
short int test2;
long int test3;
基本整形(int型)
不同的编译系统会为int型数据分配不同的存储单元,通常为2个字节(16位)或4个字节(32位)。数据是以补码的形式存储在存储单元中。
int型可分为有符号型signed int 和无符号行 unsigned int,其中signed int可省略signed。
在基本 int 型的基础上还有短整型(short int 型) 2个字节,长整型(long int 型)4字节。
具体可以参照下图
组成文本的基础是一个一个的字符,所以要让计算机能够做文本方面的处理,我们必须处理一个基础的工具就是怎么样来处理一个一个的字符,处理一个个的字符工具呢就是 字符类型。
声明字符变量 (用关键字 char表示)
char test1;
浮点型是专门用来处理实数的工具。实数在计算机里面是用浮点的形式来表示的,所以我们把它称之为浮点类型。
声明浮点变量
float test1;
如果在编程的时候遇到一个值还没有确定的浮点数,那么我们可以这样设置:
浮点类型:float(单精度实型)、double、long double(这三个类型和int、short、long 是一样的,float是处理比较小的实型数,double是处理一般的实型数,long double是处理比较大的实型数)
C语言为我们提供了品种繁多的运算符,接下来是对一些常用的运算符进行总结,以及表达式和语句的知识点的归纳
加法运算符: +
用于加法运算,相加的值可以是变量也可以是常量。如
减法运算符:-
用于减法运算,使其左侧的值减去右侧的值
乘法运算符:*
用于乘法运算,不同于数学上的’x’。
除法运算符:/
(1) 用于除法运算,不同于数学上的‘÷’。/左侧是被除数,/右侧是除数。
(2)整数除法和浮点数除法不同。浮点数除法的结果是浮点数,整数除法的结果是整数。
求模运算符:%
(1)用于整数运算,不能用于浮点数,求模运算符给出其左侧整数除以右侧整数的余数。
(2)求模运算符常用于控制程序流
(3)负数求模:如果第一个运算对象为负数,那么求模结果为负数;如果第一个运算对象为正数,那么求模结果为正数
符号运算符: +和-
用于标明或改变一个值的代数符号。它们是一元运算符(单目运算符),即只需要一个运算对象。
递增运算符:++
(1)功能:将其运算对象递增1
(2)两种形式:
1. 前缀模式:++出现在其作用的变量前面
2. 后缀模式:++出现在其作用的变量后面
(3)这种缩写形式的好处:让程序更加简洁、美观,可读性更高。
(4)前缀模式:先++,再使用 ;
后缀模式:先使用,再++
递减运算符:–
与++同理。
1.赋值运算符:=
在C语言中,=不意味着“相等”,而是一个赋值运算符,num = 1,读作“把值1赋给变量num”,赋值行为从右往左进行
C语言中不回避三重赋值,赋值顺序从右向左,如a = b = c = 100;首先把100赋给从c,然后再赋给b,最后赋给a。
2.其他赋值运算符:+=、-=、*=、/=、%=
与关系运算一样,用整数1代表“真”,用整数0代表“假”
作为if else语句的一种便携方式。是C语言中唯一的三目运算符(带三个运算对象)
通用形式:A?B:C (结合律:从左向右)
位运算符概览
位运算符
& 按位“与”
| 按位“或”
^ 按位“异或”
~ 取反
<< 左移 >> 右移
(1)位运算符中除^是单目运算符外,其它均为双目运算符
(2)位运算符所操作的操作数只能是整型或字符型的数据以及它们的变体
按位“与”:
(1)二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为1时,结果才为1。
(2)C语言中有一个按位和赋值结合的运算符:&=
按位“或”:
(1)二元运算符 | 通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位为1,结果为1。
(2)C语言中有一个按位和赋值结合的运算符:|=
按位“异或”:
(1)二元运算符^通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位一个为1,结果为1。
(2)C语言中有一个按位和赋值结合的运算符:^=
取反:
一元运算符~把1变为0,把0变为1。
左移:
(1)<<将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值丢失,用0填充空出的位置。
(2)该操作产生一个新的位值,但是不改变运算对象 。
右移:
(1)>>将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。左侧运算对象移出右末端位的值丢失。对于无符号型,用0填充空出的位置;对于有符号类型,其结果取决于机器,空出的位置可以用0填充,也可以用符号位的副本填充
(2)该操作产生一个新的位值,但是不改变运算对象 。可以使用<<=左移赋值运算符来更改变量的值
注意:移位运算具体实现有3种方式
①循环移位:移入的位等于移出的位
②逻辑移位:移出的位丢失,移入的位取0
③算术移位(带符号):移出的位丢失,左移入的位取0,右移入的位取符号位,即最高位代表数据符号,保持不变
长度运算符sizeof():单目运算符,以字节为单位返回运算对象的大小。C语言规定,sizeof()返回size_t类型的值,这是一个无符号整数类型。
函数调用运算符()
下标引用运算符[]:用来表示数组元素
强制类型转换运算符(类型):强制类型转换
查找地址 &运算符:一元&运算符给出变量的存储地址
间接(解引用)运算符*:找出存储在某变量中的值
访问结构成员(.)(->)
表达式是由运算符和运算对象组成的。
最简单的表达式是一个单独的运算对象,一些表达式由子表达式组成。
C表达式的一个最重要特性就是,每个表达式都有一个值,其值不是0就是1。
语句时C程序的基本构建块。一条语句相当于一条完整的计算机指令。
在C中,大部分语句以分号结尾。最简单的语句是空语句,即只有一个分号。C把末尾上加上一个分号的表达式都看作是一条语句,所以8;4+3;类似这样写也是可以的。虽然一条有用的语句相当于一条完整的指令,但并不是所有的指令都是语句,如:x = 6 + (y = 5);此处的y = 5便是一条完整的指令,但是它只是语句的一部分。
复合语句:用花括号括起来的一条或多条语句,复合语句也称为块。
C语言中有三种循环语句: while循环; for循环; do…while循环
当条件满足时,不断重复执行循环体中的语句。
循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行。
条件成立是循环继续的条件。
while(表达式)
{
循环语句;
}
for(表达式1;表达式2;表达式3)
每个表达式的作用:
表达式1:表达式1为初始化部分,用于初始化循环变量的。
表达式2:表达式2为条件判断部分,用于判断循环时候终止。
表达式3:表达式3为调整部分,用于循环条件的调整。
do
循环语句;
while(表达式);
进入循环时不做检查,执行完一轮循环体的代码后再检查循环的条件是否满足,若满足则继续下一轮循环,不满足则结束循环。循环体至少执行一遍。
形式1:
if (逻辑表达式)
执行语句
如果逻辑表达式为真,则执行执行语句
形式2:
if (逻辑表达式)
执行语句1
else
执行语句2
如果逻辑表示是为真则执行执行语句1,否则执行执行语句2
形式3:
if (逻辑表达式1)
执行语句1
else if (逻辑表达式2)
执行语句2
else
执行语句3
如果逻辑表达式1为真,则自行执行语句1,如果逻辑表达式1为假而逻辑表达式2为真,则执行执行语句2,否则,如果两个表达式都为假,执行执行语句3
关于if语句形式2分析:
规则是如果没有花括号致命,else与和它最近的一个if相匹配。如果希望在if和else之间有多条语句,必须使用花括号创建一个代码块。下面的结构违反了C语法,因为编译器期望if和else之间只有一条语句。
continue命令可以与三种循环形式中的任何一种一起作用,但是不能喝switch语句一起使用。他导致程序控制跳过循环中的剩余语句。对于while和for循环,开始下一个循环周期。对于do while循环,对退出条件进行判断,如果必要,开始下一个循环周期。
用处:可以在主语语句中消除一级缩排。当语句很长或者已经有很深的嵌套时,作为占位符,使代码根据可读性:
while (getchar () != '\n')
continue;
循环中的break语句导致程序终止包含它的循环,并进行程序的下一阶段。break命令可以与三种循环形式中的任何一种以及switch语句一起使用。它导致程序控制跳过包含它的循环或switch语句的剩余部分,继续执行紧跟在循环或switch后的下一条命令。
break语句实质上是switch语句的附属物,顺便提一下,break语句用于循环和switch中,而continue仅用于循环。
例如:在if语句中使用continue会出现: 错误:continue语句出现在循环以外。
结构化程序设计的思想:把大问题分解成若干个小问题,每个小问题就是一个独立的子模块,以实现特定的功能、在C程序中,子模块的作用是由函数完成的.
一个c源程序可以由多个文件构成(c文件的后缀名.c)
一个源文件是一个编译单位
一个源文件可以由若干个函数组成(函数是c程序的基本组成单位)
每个c程序只能有一个main函数,其他都是子函数。
主函数可以调用子函数,子函数可以相互多次调用。
标准函数
标准函数又称为库函数,由c系统提供,无需程序员定义,可直接使用,但需要在程序开头包含原型声明的头文件。如scanf()
自定义函数
由程序员根据自己的需求编写,自定义函数不仅要在程序中定义函数本身,必须还要在主函数中调用该函数
有返回值函数
该类函数被调用执行完毕,将向调用者返回一个执行结果,称为函数的返回值
无返回值的函数
无返回值函数不需要向主调函数提供返回值
无参函数
在函数的声明、定义和调用中均不带参数,特点:在调用无参函数主调函数并不将数据传输给被调用函数,此类函数通常被用来完成指定的功能,可以返回或不返回函数值。
有参函数
在函数定义、声明都都有参数。特点:主调函数调用被调函数时,主调函数必须把值传输给形参,以供被调函数使用
int max(int a,int b) // 有参函数
{
函数体
}
/*
函数的定义
函数类型 函数名([类型说明 变量名[,类型说明 变量名]])
{
函数体
}
注意:函数与函数的关系是同等级别,不能嵌套
*/
函数的返回值是通过函数中的return语句实现
return语句将被调函数中的一个确定值带回主调函数中
定义函数时,必须指明函数的返回值类型,而且
return语句表达式中的类型应该与函数定义时首部的函数类型时一致的,如果二者不一致,则以函数定义时首部的函数类型为准
*/
函数的参数有两类:形参和实参
函数定义时的参数称为形参,参数在函数未被调用时是没有确定值的,只是形式上的参数
函数调用时使用的参数称为实参
c语言规定,实参对形参的数据传递是“值传递”,即单向传递,只是把实参的值传递给形参,而不能把实参的值再传递给实参。再内存当中,实参与形参是不同的单元,不管名字是否相同,因此函数中对形参值的任何改变都不会影响到实参的值。
C程序是从主函数main()开始执行的,以main()函数体结束为止在函数体的执行过程中,通过不断地对函数的调用来执行的。
调用者 被称为主调函数一般为main()函数,
被调用者 称为被调函数一般为自定义函数或者库函数。
被调函数执行结束,从被调函数结束的位置再返回主调函数当中,继续执行主调函数后面的语句。
m=max(x,y); // 将max()函数的返回值赋值给变量m
m=3*max(x,y); // 将max()函数的返回值乘3赋值给变量m
printf("Max is %d,max(x,y)"); // 输出max()函数的返回值
使用原则:先定义后使用
有参函数的声明形式
函数类型 函数名(形参列表);
int tian(int a,int b);
无参函数的声明形式:
函数类型 函数名();
int tain();
在c语言中,函数的关系是平行的,是独立的即函数的不能嵌套定义。
c语言中函数的嵌套调用即在调用一个函数的过程中可以调用另外一个函数。
如果在调用一个函数的过程中,有直接或间接的调用了该函数本身,这种形式称为函数的递归调用,这个函数就称递归调用。
如果在一个源文件中定义的函数只能被文件中的函数调用,而不能被同一源程序其他文件中的函数调用。
内部函数的一般形式
static 类型说明符 函数名([形参表])
[ ]中的部分为可选项
内部函数关键字:static
static int f(int a,int b) // 内部函数
{
}
f()函数只能被本文件中的函数调用,其他文件中不能使用该函数。
外部函数在整个源程序中都有效,只要定义函数时,在前面加上extern关键字
定义形式
extern 类型说明符 函数名 (<形参表>)
extern int f(int a,int b)
{
}
数组是一组有序的数据的集合--数组中的元素类型相同,并由数组名和下标唯一地确定。
语法: type ArrayName[size];
说明:
语法: type ArrayName[size]={value-list};
存储形式:
1.数组如果不初始化,其元素值为随机数
2.可以只初始化部分数组元素,余者自动赋0值。
int a[5]={ 2 , 4 }等价于 int a[5]={ 2 , 4, 0, 0, 0 };
3.当全部数组元素赋初值时,可不指定数组长度
int a[]={1,2,3,4,5,6} ; //编译系统根据初值个数确定数组维数
4.全局数组或static数组元素自动初始化成0值
static int a[5];等价于static int a[5]={ 0 , 0, 0, 0, 0 };
数组必须先定义,后使用
只能逐个引用数组元素,不能一次引用整个数组
数组元素表示形式: 数组名[下标]
数组元素的下标:0~size-1。
数组名a代表的是数组a在内存中的首地址,也即数组元素a[0]的地址。
scanf(“%d”,&a[0]); 等价于 scanf(“%d”,a);
类型标识符 数组名[常量表达式SIZE 1][常量表达式 SIZE2];
表示形式: 数组名[行下标][列下标]
行下标、列下标可以是常量、变量、函数或表达式。如果下标带有小数,C 编译系统将自动对其取整。
C 语言对数组元素的引用有以下几种规定:
行下标、 列下标均从 0 开始计算。
数组下标表示了数组元素在数组中的顺序号。
因此,如果定义:
float Num[2][4]={ 0,1,2,3,4,5,6,7 };
则可以访问的行下标为 0、1,可以访问的列下标为 0、1、2、3。
不能越界引用数组元素Num[1][8]、Num[5][3],因为 C 语言从不检查数组下标是否越界,程序员要自己确认数组元素的正确引用,不能越界访问。
在 C 语言中只能逐个引用数组元素,而不能一次引用整个数组。
数组元素和普通变量一样,可以出现在任何合法的 C 语言表达式中。
定义数组时,方括号中出现的是某一维的长度,只能是常量或常量表达式;
引用数组元素时,方括号中出现的是该元素在数组中的位置标识,可以是常量,变量或任何合法的C 语言表达式。
字符串常量:
用一对双引号括起来的若干字符序列。
字符串长度:字符串中字符的个数。
字符串的结束标志: NULL ‘\0’,ASCII值为0。
“ ”表示空字符串,长度为0,占一个字节,即NULL。
字符数组的定义与一维数组、二维数组基本相同,类型标识符为 char。
说明:
字符数组的每一个元素都是字符变量
处理n个字符,size必须≥n+1
在这里插入图片描述
初始化:
逐个数值赋给字符数组的元素
char str[10]={112,114,111,103,114,97,109,0};
逐个字符赋给字符数组的元素
char str[10]={‘p’,‘r’,‘o’,‘g’,‘r’,‘a’,‘m’,’\0’};
用字符串常量直接初始化数组
char str[10]={“program”};
char str[10]=“program”;
字符数组定义初始化时可省略第一维长度,由系统编译时根据初值个数自动确定数组长度:
char Str[ ]=“program”; // Str数组长度自动确定为 8
通常一维字符数组用于存放一个字符串,二维字符数组用于存放多个字符串,而且至少要按最长的字符串长度加1设定第二维长度。二维字符数组定义初始化时同样只能省略第一维长度
char Subject[3][15]={“C programming”,“Java”,“Authorware”};
char Subject[][15] ={“C programming”,“Java”,“Authorware”};
逐个字符输入输出
用格式符“%c”输入或输出一个字符
将整个字符串一次输入或输出。
用格式符“%s”输入输出字符串。
注意
用“%s”格式符输入字符串时,字符数组名前不要再加地址符&,因为数组名代表该数组的起始地址。
用“%s”格式符输入字符串时,输入的字符串应≤字符数组的长度-1。系统自动在后面加 个‘\0’结束符。
结构体(struct)是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。
共用体(union),也称为联合体,是用于(在不同时刻)保存不同类型和长度的变量,它提供了一种方式,以在单块存储区中管理不同类型的数据。
声明一个结构体类型的一般形式为:
struct 结构体名 {
成员列表
};
其中,成员列表中对各成员都应进行类型声明,即:
类型名 成员名;
如果要表示图中的数据结构,但 C 语言并没有提供这种现成的数据类型,因此我们需要用定义一种结构体类型来表示。
前面只是声明了一个结构体类型,它相当于一个模型,但其中并无具体的数据,编译系统对其也不分配实际的内存单元。为了能在程序中使用结构体类型的数据,我们应当定义结构体类型的变量,并在其中存放具体的数据。
主要以下 3 中方式定义结构体类型变量:
先声明结构体类型,再定义变量名
结构体类型名 结构体变量名;
定义了 student1 和 student2 为 struct student 类型的变量,它们具有 struct student 类型的结构,后续我们可以对它们进行初始化。
在声明类型的同时定义变量例如:
struct student {
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1, student2;
它的作用与第一种方法相同,即定义了两个 struct student 类型的变量 student1、student2。这种形式的定义的一般形式为:
struct 结构体名 {
成员列表
} 变量名列表;
直接定义结构体类型变量其省略了结构体名,一般形式为:
struct {
成员列表
} 变量名列表;
关于结构体类型,需要补充说明一点:
类型与变量是不同的概念,不要混淆。我们只能对变量赋值、存取或运算,而不能对一个类型进行赋值、存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
简单地说,我们可以把“结构体类型”和“结构体变量”理解为是面向对象语言中“类”和“对象”的概念。
此外,结构体里的成员也可以是一个结构体变量。
引用结构体变量中成员的方式为:
结构体变量名.成员名
和其他类型变量一样,对结构体变量可以在定义时指定其初始值,用大括号括起来:
struct student {
int num;
char name[20];
char sex;
int age;
char addr[30];
} a = {10010, "Li Lei", 'M', 18, "Beijing Haidian"};
结构体与数组
如果一个数组的元素为结构体类型,则称其为“结构体数组”。结构体数组与之前介绍的数值型数组的不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。
定义结构体数组
和定义结构体变量的方法类似,只需声明其为数组即可
结构体数组的初始化
与其他类型的数组一样,对结构体数组可以初始化
一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素。
指向结构体变量的指针
struct student {
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
struct student stu1 = {...};
struct student * p;
p = &stu1;
上述代码先声明了 struct student 结构体类型,然后定义一个 struct student 类型的变量 stu1,同时又定义了一个指针变量 p,它指向一个 struct student 类型的数据,最后把结构体变量 stu1 的起始地址赋给指针变量 p,如图所示:
此时可以用 *p 来访问结构体变量 stu1 的值,用 (*p).num来访问 stu 的成员变量。C 语言为了使用方便和直观,定义可以把 (*p).num 改用 p->num 来代替,它表示 p 所指向的结构体变量中的 num 成员。
也就是说,以下 3 种形式等价:
结构体变量.成员名:stu1.num
(*指针变量名).成员名:(*p).num
指针变量名->成员名:p->num
指向结构体数组的指针对于结构体数组及其元素也可以用指针变量来指向
定义共用体类型变量的一般形式为:
union 共用体名 {
成员列表
} 变量
可以看到,“共用体”与“结构体”的定义形式相似,但它们的含义是不同的:
结构体变量所占的内存长度(字节总数)是各成员占的内存长度之和,每个成员都分别独占其自己的内存单元。
共用体变量所占的内存长度等于最长的成员的长度。
与结构体类似,共用体变量中成员的引用方式为:
共用体变量名.成员名
只有先定义了共用体变量才能引用它,而且不能直接引用共用体变量,只能引用共用体变量中的成员。
在使用共用体类型数据时,应当注意以下一些特点:
同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种。也就是说,每一瞬时只有一个成员起作用,其它的成员不起作用,即:共用体中的成员不是同时都存在和起作用的。
共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后,原有的成员就失去作用了。
不能直接对共用体变量名赋值,也不能企图引用变量名来得到一个值,同时也不能在定义共用体变量时对它初始化。
不能把共用体变量作为函数参数,也不能使函数返回共同体类型的变量,但可以使用指向共用体变量的指针。
共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念
在32 位平台里,指针本身占据了4 个字节的长度。
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。
指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。
两个指针可以进行减法操作,但必须类型相同,一般用在数组方面.
这里&是取地址运算符,*是间接运算符。
*p 的结果是p 所指向的东西,
特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
可以把一个指针声明成为一个指向函数的指针。
int fun1(char *,int);
int (*pfun1)(char *,int);
pfun1=fun1;
int a=(*pfun1)("abcdefg",7); //通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
宏定义在 C 语言源程序中允许用一个标识符来表示一个字符串,称为“宏/宏体” ,被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为“宏替换”或宏展开”。 宏定义是由源程序中的宏定义命令完成的,宏代换是由预处理程序自动完成的。
在 C 语言中,宏分为 有参数和无参数两种。无参宏的宏名后不带参数,其定义的一般形式为:
#define 标识符 字符串
不带参数的宏定义
#define MAX 10
带参宏定义
#define M(y) yy+3y
方便程序的修改
使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。
相对于全局变量两者的区别如下:
提高程序的运行效率
使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。
由于是直接嵌入的,所以代码可能相对多一点;
嵌套定义过多可能会影响程序的可读性,而且很容易出错,不容易调试。
对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。
宏函数,函数比较
从时间上来看
宏只占编译时间,函数调用则占用运行时间(分配单元,保存现场,值传递,返回),每次执行都要载入,所以执行相对宏会较慢。
使用宏次数多时,宏展开后源程序很长,因为每展开一次都使程序增长,但是执行起来比较快一点(这也不是绝对的,当有很多宏展开,目标文件很大,执行的时候运行时系统换页频繁,效率就会低下)。而函数调用不使源程序变长。
从安全上来看
函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
宏的定义很容易产生二义性.
调用函数只可得到一个返回值,且有返回类型,而宏没有返回值和返回类型,但是用宏可以设法得到几个结果。
函数体内有Bug,可以在函数体内打断点调试。如果宏体内有Bug,那么在执行的时候是不能对宏调试的,即不能深入到宏内部。
C++中宏不能访问对象的私有成员,但是成员函数就可以。
内联函数
内联函数和宏的区别在于,宏是由预处理器对宏进行替代 ,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样展开,所以取消了函数的参数压栈,减少了调用的开销。可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
宏函数的适用范围
一般来说,用宏来代表简短的表达式比较合适。
在考虑效率的时候,可以考虑使用宏,或者内联函数。
还有一些任务根本无法用函数实现,但是用宏定义却很好实现。比如参数类型没法作为参数传递给函数,但是可以把参数类型传递给带参的宏。
宏是一种字符串替换不做类型检查,可以将类型做为参数传入宏函数,利用这种特性可以实现通用数据结构的封装,以动态数组darray,和循环链表list为例
动态数组
动态数组是把自己的结构体放在规定的结构体之内,还有一种实现方式,把规定的结构体放到自己的结构体之中,这种方式扩展性更好,这个时候需要根据成员指针得到结构体指针。通过container_of实现。
循环链表list
__FUNTION__ 获取当前函数名
__LINE__ 获取当前代码行号
__FILE__ 获取当前文件名
__DATE__ 获取当前日期
__TIME__ 获取当前时间
__STDC_VERSION__
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。