一、SIZEOF基本概念
sizeof是C语言的关键字,它用来计算变量(或数据类型)在当前系统中占用内存的字节数。
sizeof不是函数,产生这样的疑问是因为sizeof的书写确实有点像函数,sizeof有两种写法:
用于数据类型
sizeof(数据类型);
数据类型必须用括号括住。
printf("字符型变量占用的内存是=%d\n",sizeof(char)); // 输出:字符型变量占用的内存是=1
printf("整型变量占用的内存是=%d\n",sizeof(int)); // 输出:整型变量占用的内存是=4
用于变量
sizeof(变量名);
sizeof 变量名;
变量名可以不用括号括住,带括号的用法更普遍,大多数程序员采用这种形式。
1、sizeof(结构体)
理论上讲结构体的各个成员在内存中是连续存放的,和数组非常类似,但是,结构体占用内存的总大小不一定等于全部成员变量占用内存大小之和。在编译器的具体实现中,为了提高内存寻址的效率,各个成员之间可能会存在缝隙。用sizeof可以得到结构体占用内容在总大小,sizeof(结构体名)或sizeof(结构体变量名)都可以。
示例(book90.c)
/*
* 程序名:book90.c,此程序用于演示C语言的结构体占用内存的情况
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
// #pragma pack(1) # 告诉编器内存按1字节对齐。
struct st_girl
{
char name[50]; // 姓名
int age; // 年龄
int height; // 身高,单位:厘米cm
char sc[30]; // 身材,火辣;普通;飞机场。
char yz[30]; // 颜值,漂亮;一般;歪瓜裂枣。
};
int main()
{
struct st_girl queen;
printf("sizeof(struct st_girl) %d\n",sizeof(struct st_girl));
printf("sizeof(queen) %d\n",sizeof(queen));
}
运行效果
从上面的示例可以看出,struct st_girl全部成员变量占用的内存是50+4+4+30+30=118,但是结构体占用的内存是120。
注意,C语言提供了结构体成员内存对齐的方法,可以使结构体成员变量之间的内存没有空隙,启用#pragma pack(1)代码即可。
2、不要对void使用sizeof
printf("sizeof(void)=%d\n",sizeof(void)); // 输出sizeof(void)=1
以上代码在有些编译器中可能无法通过。
void是无值型或空类型,不知道存储空间大小的类型,编译器也不能确定它的大小。void不能声明变量,以下代码编译无法通过:
void vv;
但是以下代码是正确的:
void *pv;
printf("sizeof(void*)=%d\n",sizeof(pv)); // 输出sizeof(void)=8
pv是一个void指针,在64位操作系统中,指针变量占用的内存的大小都是8,下同。
3、不要在子函数中对字符指针用sizeof
如果把一个字符串(如char strname[21])的地址传给子函数,子函数用一个字符指针(如char *pstr)来存放传入的字符串的地址,如果在子函数中用sizeof(pstr),得到的不是字符串占用内存的字节数,而是字符指针变量占用内存的字节数(8字节)。
所以,不能在子函数中对传入的字符串进行初始化,除非字符串的长度也作为参数传入到了子函数中。
4、不要在子函数中对结构体指针用sizeof
如果把一个结构体(如struct st_girl stgirl)的地址传给子函数,子函数用一个结构体指针(如struct st_girl *pgril)来存放传入的结构体的地址,如果在子函数中用sizeof(pgirl),得到的不是结构体占用内存的字节数,而是结构体指针变量占用内存的字节数(8字节)。正确的用法是用sizeof(struct st_girl)。
二、C语言运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
() | 圆括号 | (表达式)/函数名(形参表) | -- | ||
. | 成员选择(对象) | 对象.成员名 | -- | ||
-> | 成员选择(指针) | 对象指针->成员名 | -- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | -- | ||
sizeof | 长度运算符 | sizeof(表达式) | -- | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | -- |
/= | 除后赋值 | 变量/=表达式 | -- | ||
*= | 乘后赋值 | 变量*=表达式 | -- | ||
%= | 取模后赋值 | 变量%=表达式 | -- | ||
+= | 加后赋值 | 变量+=表达式 | -- | ||
-= | 减后赋值 | 变量-=表达式 | -- | ||
<<= | 左移后赋值 | 变量<<=表达式 | -- | ||
>>= | 右移后赋值 | 变量>>=表达式 | -- | ||
&= | 按位与后赋值 | 变量&=表达式 | -- | ||
^= | 按位异或后赋值 | 变量^=表达式 | -- | ||
|= | 按位或后赋值 | 变量|=表达式 | -- | ||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | -- |
说明:
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
三、语句和程序块
一个函数包含声明部分和执行部分。执行部分由语句组成,经编译后产生机器指令。声明部分不是语句,不产生机器指令。一个C程序的结构如下:
分类
C语句分为以下5类
(1). 控制语句
- 条件语句:if(表达式) … else if(表达式) … else …
- 循环语句:
(1). for(表达式1; 表达式2; 表达式3) …。在C99标准中允许表达式1定义变量并赋予初值。推荐在这里给仅在循环中用到的变量定义,因为这样能缩短变量的作用域
(2). while(表达式) …
(3). do … while(表达式);(注意最后有分号)
多层循环嵌套时,一般长循环放在内层,短循环放在外层 - 跳过本次循环语句:continue
- 结束switch或循环语句:break
- 多分支选择语句:switch。
switch(整型数据(包括枚举型)或字符型数据) {
case 常量1: 语句1; break;
case 常量2: 语句2; break;
case 常量3:
case 常量4: 语句3; break;//此时常量3和常量4都进入到语句3
...
default: 语句n;(没有default时不符合则跳过)
}
//对于if、switch等选择语句,通常把正常情况,也即出现可能性最大的分支放前面,而可能性小的分支放后面
(2). 函数调用语句
如printf(“Hello world.”);
(3). 表达式语句
如赋值表达式a=3;,++i;等
(4). 空语句
直接只有一个分号的一行语句。
(5). 复合语句
用{}将语句和声明括起来的复合语句,也称语句块
1.常用语句
C程序中最常用的是赋值语句和输入输出语句
2.赋值语句
复合赋值运算符中,x%=y+3、x%=(y+3)、与x=x%(y+3)三者等价
赋值表达式的一般形式为,变量 赋值运算符 表达式三项。其中,算术表达式、常量、以及赋值表达式不能作为左值
连等式中a=(b=5)与a=b=5等价,也即连等式的运算顺序为从右到左
在赋值过程中将占字节多的数据赋值给占字节少的变量时,直接截取相应数据范围的部分
3.输入输出语句
(1). printf
一般形式为printf(格式控制,输出表列)
- d格式符。用来输出有符号十进制整数。%5d表示数据占据5列且数据靠右,若想靠左则为%-5d。但%+d则表示输出整数前的正负号。长整型则为%ld,双长整型为%lld。l可放在d、o、x、u前。而在scanf中%5d则表示只读取前5位整数
- c格式符。用来输出一个字符
- s格式符。用来输出一个字符串。%5s在printf中表示只输出前5个字符,在scanf中表示只读取前5个字符
- f格式符。用来输出实数
(1). 基本型用%f。实数部分全部输出,小数部分输出6位
(2). 指定数据宽度和小数位数%m.nf。如果n为0则不会输出小数点。注意其中m是指包含小数点在内的整个浮点数长度而不单单指整数部分 - e格式符。用来输出指数形式实数。同样可以用%m.ne形式声明。也可用写成%E,此时输出指数中为大写E
- i格式符。在 printf 中没有区别,在 scanf 时,%d 只与十进制形式的整数相匹配;而%i 则可以匹配八进制、十进制、十六进制表示的整数。 如输入的数字有前缀 0(018、025),%i会把它当作八进制数来处理,如果有前缀0x (0x54),它将以十六进制来处理
- o格式符。连同符号位一起以八进制整数形式输出
- x格式符。连同符号位一起以十六进制整数形式输出。%X时以大写形式输出
- u格式符。用来输出无符号整数
- g格式符。用来输出浮点数,并由系统自动选择为f格式或e格式输出。%G时以大写形式输出
- p格式符。用来输出地址
(2). scanf
一般形式为scanf(格式控制,地址表列)
其中,l可用于输入长整型数据如ld、lo、lx、lu以及double型数据lf、le。h可用于输入短整型数据如hd、ho、hx
在scanf(“a=%d, b=%d”, &a, &b);中输入则必须为a=1, b=2。若为scanf(“a=%d b=%d”, &a, &b);则a=1与b=2之间必须要有1个以上的空白字符
scanf和printf函数都有返回值,scanf返回正确读入了多少个变量,printf返回输出了包括换行符在内的有多少个字符
(3). sprintf
一般形式为sprintf(目标字符串,格式控制,地址表列)
sprintf函数用于将字符串输出到目标字符串而不是屏幕中,要注意字符串的长度要足够容纳打印的内容,否则会出现内存溢出
(4). sscanf
一般形式为sscanf(源字符串,格式控制,地址表列)
sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。sscanf可用来进行数据类型或进制转换以及按特定规则读取源字符串的特定规则。也即使用正则表达式,但是功能并没有正则表达式强大,并且使用时有较多限制。同理,在scanf以及printf中的格式控制都能使用正则表达式。C语言并不直接支持正则表达式,而需要通过函数库regex.h来完成
几种常见的表达方法:
- %[abc]读取a、b、或c的任意一员,[a-z]表示匹配小写字母,[a-z0-9]表示匹配小写字母及数字
- %[^a-z]读取不在a-z之间的字符串,如果碰到 a-z 之间的字符则停止
- %[^=]读取字符串直到碰到=号
- %*[^=]%s前面带*号表示不保存变量。跳过字符串中以=结尾的子串而读后包括=在内的后面部分子串
另外,可用*匹配变量到格式字符串中,如printf("%*d\n", a, A);即输出a长度宽的整数A,而printf("%.*lf\n", a, A);则输出a位小数的浮点数A
(4). 字符输入输出
- putchar( c )。用于输出字符变量c。若c为整数则当ASCII码处理
- c = getchar()。用于输入一个字符,包括控制字符。注意getchar函数返回的不是char类型而是int类型,此时有可能出现潜在错误。一般用法为while((a = getchar() && a!=EOF) != ‘\n’) { }。EOF是指检测文件尾,在头文件中通过宏定义定义值为-1。键盘输入EOF其实是命令行环境遗留下来的,但是很多系统支持仿真的EOF,如用Ctrl+D(Linux系统)或Ctrl+Z(Windows系统)来模拟输入EOF
判断语句
一、if(…) {…}
1.一般形式:
if (表达式) {语句;}
表达式:
a,用非 0 值表示真,用 0 表示假;
b,if(flag) 相当于 if(flag!=0);
c,浮点数无法与 0 比较,只能用近似的值比较;例: 1e-6 等于1x10的-6次方可以看成0来使用;
2.用于单分支选择结构;
3.如含有交叉关系,使用并列的if语句;
例1:输出两个整数中的最大值
#include <stdio.h>
void main()
{
int a,b;
printf("请输入两个整数:");
scanf("%d %d",&a,&b);
if(a>b)
{
printf("max = %d\n",a);
}
if(a<b)
{
printf("max = %d\n",b);
}
}
二、if(…) {…} else {…}
- 一般形式:
if(表达式) {语句1; } else { 语句2;}
- 用于双分支控制的条件语句;
- 用于非此即彼的关系时;
例2:输出两个整数中的最大值
例2关于对于非法字符的检查与处理
#include <stdio.h>
void main()
{
int a,b,max,data;
printf("Input a,b:");
data = scanf(" %d,%d",&a,&b);/*记录scanf()的返回值;*/
if(data!=2)/*根据scanf()的返回值判断输入数据个数或者格式是否错误*/
{
printf("格式输入错误!");
fflush(stdin);/*清除输入缓冲区中的错误内容(fflush()可能带来可移植性的问题)*/
}
else
{
max = a>b?a:b;/*三目运算符(a>b值为真则输出a的值,反之输出b的值)*/
printf("%d\n",max);
}
}
三、if(…) {…} else if(…) {…} … else if(…) {…} else {…}
-
一般形式:
if(表达式1) { 语句1;}
else if(表达式2) {语句2;}
…
else if(表达式n){语句n;}
else{语句n+1;} -
用于多分支的控制的条件语句;
例3:判断字符
#include <stdio.h>
void main()
{
char ch;
printf("请输入一个字符:");
ch = getchar();/*getchar、putchar专门用于字符输入输出;
getchar()写法上要写为 变量 = getchar();
putchar()用法为putchar(变量);
putchar('\n')输出一个控制符;
putchar('字母/字符');输出字母/字符;
*/
if(ch<=31)
{
printf("这是一个控制字符或通讯专用字符!\n");
}
else if(ch >= '0' && ch <= '9')
{
printf("这是一个数字!\n");
}
else if(ch >= 'A' && ch <= 'Z')
{
printf("这是一个大写字母!\n");
}
else if(ch >= 'a' && ch <= 'z')
{
printf("这是一个小写字母!\n");
}
else
{
printf("这是其他字符!\n");
}
}
四、switch() {case …: …; case …: …; … default: …;}
- 一般形式:
switch(表达式)
{
case 常量1:语句1;
case 常量2:语句2;
…
case 常量n:语句n;
default: 语句n+1;
} - 用于多路选择的语句;
switch语句相当于多个if-else语句;
(表达式)只能是char型或者int型;
case 后面至少要有一个空格,常量后面是冒号
(表达式)与 常量类型要保持一致;
记得记得记得 在需要跳出的时候,在语句后面加上break;
例4:简单的加减乘除计算
#include <stdio.h>
void main()
{
double a,b;
char ch;
printf("Input a(+ - * /)b:");
scanf("%f%c%f",&a,&ch,&b);
switch(ch)
{
case '+':
printf("%f%c%f=%.2f\n",a,ch,b,a+b);/*%.2f表示精度,可以理解为保留两位小数*/
break;
case '-':
printf("%f%c%f=%.2f\n",a,ch,b,a-b);
break;
case '*':
case 'X':
case 'x':
printf("%f%c%f=%.2f\n",a,ch,b,a*b);/*输入"x" "X" "*" 都执行这一条语句;不加break,会顺语句执行*/
break;
case '/':
printf("%f%c%f=%.2f\n",a,ch,b,a/b);
break;
default:
printf("请输入正确算式!\n");
}
}
扩展
5.1:C语言中输出格式%m.nf的意思
- m:表示宽度
- .n:表示精度
假设,
i=10.1;
printf("%5.2f",i);
输出结果为 10.10(m=5,其中空格占1位,小数(n)占2位)
循环语句
循环语句是用于重复执行某条语句(循环体)的语句,它包含一个控制表达式,每循环执行一次都要对控制表达式进行判断,如果表达式为真,则继续执行循环。C语言提供了3中循环语句,分别为while
语句,do while
语句和for
语句。
while语句
while语句是控制表达式在循环体之前的循环语句,它的格式如下:
while (表达式)
语句
这里的圆括号是强制要求的,圆括号之内的表达式为控制表达式,圆括号之外的语句为循环体。
while
语句的执行步骤,首先计算控制表达式的值,如果表达式的值不为0
(为真),则执行循环体,接着再次判断控制表达式,如果其值不为0
,再次执行循环体。执行这个步骤直到控制表达式的值为0
时停止。
/*************************************
* using_while_1.c *
* *
* 简单的while语句 *
*************************************/
#include <stdio.h>
int main()
{
int i = 1;
int sum = 0;
while(i <= 50)
{
sum += i;
i++;
}
printf("1到50之间(包括1和50)的整数之和为%d\n", sum);
return 0;
}
如果while
的控制表达式一直不为0,那么循环将一直进行下去,称为无限循环。这样的循环在循环体中都包含break
,goto
,return
或者导致程序终止的函数(如exit
等),以在适当的时机终止程序。
/****************************************
* using_while_2.c *
* *
* 无限循环与break *
****************************************/
#include <stdio.h>
int main()
{
int i = 1;
int sum = 0;
while(1)
{
if ( i > 50)
break;
sum += i;
i++;
}
printf("1到50之间(包括1和50)的整数和为%d\n", sum);
return 0;
}
do while
语句
do while
语句与while
语句非常相似,只不过do while
语句在每次执行完循环体之后对控制表达式进行判断的,因此do while
语句的循环体至少会执行一次,而while
语句的循环体可能一次都不被执行。do while
语句的格式如下:
do
语句
while(表达式);
其中控制表达式两端的括号也是必须的。
do while
循环的执行步骤如下:首先执行循环体,再计算控制表达式的值,如果表达式的值非零再继续执行循环体,然后再次判断表达式的值。这个过程持续进行,知道控制表达式的值为0,终止do while
语句执行。
建议对do while
的循环体都使用大括号包裹起来,因为大括号会使do while
语句被误认为是while
语句。
/*************************************
* using_do_while.c *
* *
* C语言中的do while循环 *
*************************************/
#include <stdio.h>
int main()
{
int i = 1;
int sum = 0;
do
{
sum += i;
i++;
}while(i <= 50);
printf("1和50之间(包括1和50)的整数之和为%d\n", sum);
return 0;
}
for
语句
for
语句的格式如下:
for (表达式1; 表达式2; 表达式3)
语句
表达式1是初始化步骤,只执行一次,表达式2是控制表达式,控制循环的终止,表达式3在每次循环的最后被执行。
for
语句的执行步骤是:首先执行表达式1进行初始化,然后判断表达式2的值是否为真,若为真,则执行循环体,然后执行表达式3.随后再次对表达式2的值进行判断,若为真,则再次执行循环体和表达式3,依次循环直到表达式2的值为0为止。
for
语句的3个表达式都可以省略,若省略表达式1,则在执行循环前没有初始化的操作;若省略第三个表达式,则执行完循环体后,没有再需要执行的语句,循环体确保循环能够在有限时间内终止;若省略第二个表达式,则每次判断都默认为真,除非在循环体内使用break
,goto
和程序终止函数外,循环不会停止。
/*************************************
* using_for.c *
* *
* C语言的for语句 *
*************************************/
#include <stdio.h>
int main()
{
int sum = 0;
int i;
for (i = 1; i <= 50; i++)
{
sum += i;
}
printf("1和50之间(包括1和50)的整数之和为%d\n", sum);
return 0;
}
循环体中的跳转语句
在循环体中可以使用break
,continue
和goto
跳转语句。
break
语句。
break
语句在循环体的中间设置退出点,用以跳出while
,do while
和for
循环,直接执行循环语句后面的语句。除此之外,break
语句还用于switch
语句,用以跳出switch
语句而直接执行switch语句后面的语句。对于嵌套的循环语句和(或)switch
语句,break
只能跳过其所在的那层循环或switch
语句。
continue
语句。
continue
语句并不跳出循环,而是将程序执行正好转移到循环体末尾处,跳过本次循环中循环体余下的内容。continue
语句只能用于循环体中。
/**************************************
* using_continue.c *
* *
* C语言中用于循环体的continue语句 *
**************************************/
#include <stdio.h>
int main()
{
int n = 0;
int sum = 0;
while (n < 10)
{
int i = 0;
scanf("%d", &i);
if (i == 0)
continue;
sum += i;
n++;
}
printf("以上十个非零数的和为:%d\n", sum);
return 0;
}
goto
语句。
goto
语句不仅可以用在循环体中,还可以用在程序的任何位置。与continue
和break
不同,它可以跳转到同一个函数中任何有标记的语句处。goto
依赖于定义在语句开头的标识符,其形式为:标识符 : 语句
。goto
语句的形式为:goto 标识符;
goto
易于造成程序混乱,代码难以阅读,并且其他跳转语句、return
和exit
语句能够满足大多数需要goto
语句的情况。除非万不得已,尽量不要使用。
/**************************************
* using_goto_in_loop.c *
* *
* C语言循环语句的循环体中使用goto语句*
**************************************/
#include <stdio.h>
int main()
{
int i = 0;
while (i < 10)
{
switch(i)
{
case 0:
case 1:
case 2:
case 3:
printf("%d 小于 4\n", i);
break;
default:
goto bigThan4;
}
i++;
}
bigThan4: printf("i开始不小于4\n");
return 0;
}