c语言参考手册

2.1 关键字(32位操作系统)

2.1.1 数据类型相关的关键字
用于定义变量或者类型
类型 变量名:
char、short、int、long、float、double、
struct、union、enum、signed、unsigned、void
1、char:使用char定义的变量是字符型变量,占1个字节
char ch='a';//=为赋值号
char ch1=‘1’; //正确的
char ch2= ‘1234’;//错误的
2、short:使用short定义的变量是短整型变量,占2个字节
short int a=11;   //-32768----32767
3、int:使用int定义的变量是整型变量,占4个字节

int a=44;   // -20亿---20亿
4、long:使用long 定义的变量是长整型的,占4个字节

long int a=66;
5、float:使用float定义的变量是单浮点型的实数,占4个字节
float b=3.8f;
6、double:使用double定义的变量是双浮点型的实数,占8个字节

double b=3.8;
7、struct:与结构体类型相关的关键字,可以用来定义结构体类型
8、union:与共用体(联合体)相关的关键字
9、enum:与枚举类型相关的关键字
10、signed:定义char、整型(short、int、long)数据时,使用signed修饰,代表定义的数据是有符号的,可以保存正数、负数和0
例:signed int a=10;
signed int b=-6;
注意:默认情况下signed可以省略,即int a=-10; //默认a是有符号类型的数据
11、unsigned:定义char、整型(short、int、long)数据时,用unsigned修饰,代表定义的数据是无符号的,只能保存正数和0
unsigned int a=101;
unsigned int a=-101;//错误

扩展(内存存储):
char ch=’a’;//占1个字节,存储内容是97
0110  0001
字节是内存的基本单位,8位为1个字节
计算机进行存储时,只能存储1和0的二进制组合,1和0都占1位
字符型数据在内存中不是存储字符本身,而是存储其对应的ASCII码值

整型数据在内存中存储其值的二进制
扩展:

正数在内存中以原码形式存放,负数在内存中以补码形式存放
正数的原码、反码和补码相等
原码:将一个整数转换成二进制,如单字节的5的原码为:0000 0101;-5的原码为1000 0101
反码:负数的反码是将原码除符号位外的每一位取反,如单字节的5的反码为:0000 0101;-5的反码为1111 1010

补码:负数的反码加1 ,如单字节的5的补码为:0000 0101;-5的补码为1111 1011
无论是正数还是负数,编译系统都是按照内存中存储的内容进行位运算
12、void(空类型):无法定义变量,没有void类型的变量
void用来修饰函数的参数或者返回值,代表函数没有参数或者返回值
例:
void fun(void)
{
}
例2:
#include <stdio.h>
int main()
{
char a =’a’;
short int b 10;
int c;
long int d;
float e;
double f;
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(b));
printf("%d\n",sizeof(c));
printf("%d\n",sizeof(d));
printf("%d\n",sizeof(e));
printf("%d\n",sizeof(f));
return 0;
}
2.1.2存储相关关键字
register、static、const、auto、extern
1、register是寄存器的意思,register修饰的变量是寄存器变量,即编译时编译器会尽量将其存储空间分配在寄存器中
注意:
(1)定义的变量不一定存放在寄存器
(2)cpu在寄存器中读取数据比在内存中快
(3)由于寄存器比较宝贵,所以无法定义寄存器数组
(4)register只能修饰字符型数据和整型数据,不能修饰浮点型数据,如
register char ch;
register short int b;
register int c;
register float d;//错误
(5)由于register修饰的变量可能存放在寄存器中而没有存放在内存中,所以
无法对寄存器变量取地址,只有存放在内存中的数据才有地址
register int a;
int *p;
p=&a;//错误,a可能没有地址
2、static是静态的意思,static可以修饰全局变量、局部变量、函数和指针
3、const是常量的意思,const修饰的变量是只读的,不能修改内容
const int a=101;
a=111;//错误
4、auto int a;和int a;是等价的,auto基本不用
5、extern是外部的意思,一般用于函数和全局变量的声明
2.1.3控制语句相关的关键字
if、else、break、continue、for、while、do、switch case
goto、default
2.1.4其他关键字
sizeof、typedef、volatile
1、sizeof测变量和数组占用存储空间的大小(字节)
例3:
int a=10;
int num;
num=sizeof(a);
2、typedef给一个已有的类型重命名,并没有创造一个新的类型,如
INT16 a;
U8 ch;
INT32 b;

在c语言中没有INT16和U8等类型名,它们是用typedef定义的新类型名,是short int及unsigned char的别名
typedef重命名方法:
1、使用需要重命名的类型定义一个变量
short int a;
2、使用新类型名替代需要重命名的类型名
short int INT16;
3、使用typedef修饰
typedef short int INT16;
4:新类型名可以用于定义变量
INT16 b;和short int b;等效

例4:
#include <stdio.h>
typedef short int INT16;
int main(int argc,char *argv[])
{
short int a=101;
INT16 c=111;
printf("a=%d\n",a);
printf("c=%d\n",c);
return 0;
}
3、volatile是易改变的意思,volatile修饰的变量是易改变的,即cpu每次使用volatile修饰的变量时,都需要在内存中读取,以保证使用变量的最新值,而不是寄存器中的备份,volatile现在较少使用
volatile int a=10;
变量命名规则:
在c语言中给变量和函数命名时,必须以字母或者下滑线开头
例5:
int a2;//正确
int a_2;//正确
int _b;//正确
int 2b;//错误
int $a2;//错误
变量命名要求见名知意
Linux风格,如stu_num
驼峰风格,如StuNum
大小写敏感,如
int Num;
int num;
c语言的程序结构:一个完整的c语言程序是由唯一且必须有的main()函数(又称主函数)和若干个其它可选函数构成的,程序从main()函数开始执行
2.2数据类型
2.2.1基本类型
char、short int、int、long int、float、double
常量:程序运行过程中其值无法改变
例:
>整型      100,125,-100,0
>实型      3.14,0.125f,-3.789
>字符型   ‘a’,‘b’,‘2’
>字符串    “a”,“ab”,“1232”

变量:其值可以改变,如
int a=100;
a=101;
字符数据:
>字符常量:
直接常量:用单引号括起来,如:’a’、’b’、’0’等
转义字符:反斜杠“\”后加一个或几个字符,如’\n’、’\t’、 ‘\\’等,分别代表换行、横向跳格、“\”
>字符变量:使用char修饰,每个字符变量分配一个字节的内存空间,以ASCII码值的形式存放,如

char a;
a = ‘x’;
//a存放字符’x’的ASCII码值120,即a=120;和 a=’x’;本质上是一致的
例6:
#include <stdio.h>
int main(int argc,char *argv[])

{

char a=’x’;
char b=120;
printf("a=%c\n",a);
printf("b=%c\n",b);
return 0;
}

例7
#include <stdio.h>
int main(int argc,char *argv[])
{

unsigned int i;
for(i=0;i<=255;i++)
{
printf("%d %c\n"i,i);
if(i%10==0)
printf("\n");
}
return 0;

}
字符串常量是由双引号括起来的字符序列,如“CHINA”、”哈哈哈”、
“c program”,“$12.5”等

字符串常量与字符常量的区别:
‘a’为字符常量,”a”为字符串常量,但每个字符串的结尾都有一个结束标志位’\0’,即“a”包含两个字符‘a’和’\0’
整型数据
>整型常量(按进制分):
十进制:以数字1~9开头,如457 789;转化成二进制使用除2取余法
八进制:以数字0开头,如0123
十六进制:以0x开头,a=10,b=11,c=12,d=13,e=14,f=15,如0xle
>整型变量:
>有/无符号短整型(un/signed) short int,占2个字节
>有/无符号基本整型(un/signed) int,占4个字节
有/无符号长整型(un/signed) long (int),占4个字节

实型数据(浮点型)
>实型常量:也称为实数或者浮点数
十进制形式:由数字和小数点构成,如0.0、0.12、5.0
指数形式:如123e3代表123*10^3,123e-3代表123*10^(-3)
>不以f结尾的常量是double类型
>以f结尾的常量是f1oat类型
>实型变量
单精度(float)和双精度(double)
float型:占4字节,7位有效数字,指数范围是-37~38,如3333.333
double型:占8字节,16位有效数字.指数范围是-307~308
格式化输出字符:
%d:十进制有符号整数        %u:十进制无符号整数
%x:十六进制整数            %o:八进制整数
%f:float型浮点数           %lf:double型浮点数
%e:指数形式的浮点数         %c:单个字符
%s:字符串
%p:指针的值
特殊应用:
%3d:要求宽度为3位,不足3位,前面空格补齐;足够3位,此语句无效
%03d:要求宽度为3位,不足3位,前面“0”补齐;足够3位,此语句无效
%-3:要求宽度为3位,不足3位,后面空格补齐;足够3位,此语句无效
%.2f:只保留2位小数
2.2.2构造类型
概念:由若干个相同或不同类型数据构成的集合,如数组、结构体、共用体、枚举等
例:int a[10];

2.23类型转换

不同类型数据进行混合运算时必然涉及到类型转换问题

转换的方法有两种:
自动转换:遵循一定的规则,由编译器自动完成

强制类型转换:将表达式的运算结果强制转换成所需的数据类型
>自动转换的原则:
1、占用内存字节数少(值域小)的类型向占用内存字节数多(值域大)的类型进行转换,保证精度不降低
2、转换方向:

1)表达式出现char、short int、int类型中的一种或者多种而没有其它类型时,参与运算的变量全部转换成int类型,结果也是int类型

 

例8:
#include <stdio.h>
int main(int argc,char *argv[])

{
printf(%d\n",5/2);
return 0;

}
2)表达式出现浮点数时,参与运算的变量全部转换成double类型,结果也是double型

例9:
#include <stdio.h>
int main(int argc,char *argv[])

{
printf("%lf\n",5.0/2);
return 0;

}
3)表达式出现有符号数和无符号数时,参与运算的变量转换成无符号数,结果也是无符号数  
例10:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a=-8;
unsigned int b=7;
if(a+b>0)
{
printf("a+b>0\n");
}

else
{
printf("a+b<=0\n");
}
printf("%x\n",(a+b));
printf("%d\n",(a+b));
return 0;

}
4)赋值语句中等号右边的类型自动转换成等号左边的类型
例11:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a;
float b=5.8f;//代表5.8是float类型,没有f就默认是double类型
a=b;
printf("a=%d\n",a);
return 0;
}
5)注意:自动类型转换都是在运算过程中进行临时性的转换,并不会影响自动类型转换的变量的值和其类型
例12:
#include <stdio.h>
int main(int argc,char *argv[])

{
int a;
float b=5.8f;

a=b;
printf("a=%d\n",a);
printf("b=%f\n",b);//b依然是float类型,其值依然是5.8
return 0;
}

强制转换:通过类型转换运算实现,(类型说明符) (表达式)
功能:将表达式的运算结果强制转换成类型说明符所表示的类型
例:
(float)a;∥将a的值转换成实型
(int)(x+y);∥将x+y的结果转换成整型
注意:类型说明符必须加括号
例13:
#include <stdio.h>
int main(int argc,char *argv[])
{
float x=3.6f;
int i;
i=x;

printf("i=%d\n",i);
i=(int)x;
printf("i=%d\n",i);
return 0;

}
说明:无论强制转换或者自动转换,都只是为了本次运算的需要,而对变量的数据长度进行临时性转换,但不会改变数据的类型和值
2.3运算符

2.3.1运算符
使用算术运算符将运算对象(也称操作数)连接起来的且符合c语法规则的式子,称为c算术表达式,运算对象包括常量、变量、函数等
2.3.2运算符的分类:
1、双目运算符:参加运算的操作数有两个
例:
a+b
2、单目运算符:参加运算的操作数只有一个,如+自增运算符和-自减运算符

例:
int a=10;
a++;
3、三目运算符:参加运算的操作数有3个,如()?():()
2.3.3算数运算符
+ - * / % += -= *= /= %=
复合运算符,如

a+=3相当于a=a+3
a*=6+8相当于a=a*(6+8)
2.3.4关系运算符
>、<、==、>=、<=、!=
一般用于条件判断或者循环语句
2.3.5逻辑运算符
1、&&逻辑与:两个条件都为真,则结果为真,如
if((a>b)&&(a<c))
if(b<a<c)//表达方式错误
2、‖逻辑或:两个条件至少有一个为真,则结果为真,如
if((a>b)‖(a<c))
3、!逻辑非:条件真假颠倒,如
if(!(a>b))
2.3.6位运算符
1、&按位与:任何值与0得0,与1保持不变
功能:使某位清0
0101 1011&
1011 0100

------------
0001 0000
2、|按位或:任何值或1得1,或0保持不变
0101 0011|
1011 0100

------------
1111 0111
3、~按位取反:1变0,0变1
~0101 1101

------------
1010 0010
4、^按位异或:相异得1,相同得0
1001 1100^
0101 1010

------------
1100 0110
5、位移
>>右移
<<左移
注意:右移分有逻辑右移和算数右移
(1)右移
A)、逻辑右移:无论是有符号数还是无符号数都是低位溢出,高位补0
0101 1010  >>3

------------
0000 1011
B)、算数右移:对有符号数来说,低位溢出,高位补符号位
1010 1101  >>3

------------
1111 0101
在一个编译系统中是逻辑右移,还是算数右移,取决于编译器
#include <stdio.h>
int main(int argc,char *argv[])

{
printf("%d\n",-1>>3);
return 0;

}
//如果结果还是-1,则是算数右移
(2)左移:低位补0,高位溢出
0000 0101  <<1

------------
0000 1010
2.3.7条件运算符号
()?():()
如果“?”前边的表达式成立,则表达式的值是“?”和“:”之间的表达式的结果,否则是“:”之后的表达式的结果
例14:
#include <stdio.h>
int main(int argc,char *argv[])

{
int a;
a=(3<5)?(8):(9);
printf("a=%d\n",a);
return 0;
}
2.3.8逗号运算符
(),():结果是后边表达式的结果
例15:
#include <stdio.h>
int main(int argc,char *argv[])

{
int num;
num=(5),(6);
printf("%d\n",num);
return 0;
}
2.3.9自增自减运算符
i++、i--:在当前语句中先使用i的值,在下一条语句中i的值加1
例16:
#include <stdio.h>
int main()
{

int i=3;
int num;
num=i++;
printf("num=%d,i=%d\n",num,i);//num=3,i=4
return 0;

}

例17:
#include <stdio.h>
int main(int argc,char *argv[])
{
int i=3;
int num;
num=(i++)+(i++)+(i++);
printf("num=%d\n",num);
return 0;
}
++i、--i:在当前语句中i的值先加1,然后再使用

例18:
#include <stdio.h>
int main()
{
int i=3;
int num;
num=++i;
printf("num=%d,i=%d\n",num,i);//num=4,i=4
return 0;
}

例19:
#include <stdio.h>
int main(int argc,char *argv[])

{
int i=3;
int num;
num=(++i)+(++i)+(++i);
printf("num=%d\n",num);
return 0;
}
2.3.10运算符优先级及结合性
运算符优先级:在表达式中按照优先级先后进行运算,优先级高的先于优先级低的进行运算,优先级相同的按结合性进行运算
运算符结合性:

左结合性:从左向右运算,如
int a;
a=2+3+9+10;
右结合性:从右向左运算,如
int a,b,c,d;
a=b=c=d=100;

优先级和结合性表:

优先级别

运算符

运算形式

结合方向

名称或含义

1

()

[]

.

->

(e)

a[e]

x.y

p->x

自左向右

圆括号

数组下标

成员运算符

用指针访问成员的指向运算符

2

-+

++--

!

~

(t)

*

&

sizeof

-e

++x或x++

!e

~e

(t)e

*P

&x

sizeof(t)

自右至左

负号和正号

自增运算和自减运算

逻辑非

按位取反

类型转换

指针运算,由地址求内容

求变量的地址

求某类型变量的长度

3

*/%

e1*e2

自左至右

乘、除和求余

4

+-

el+e2

自左至右

加和减

5

<< >>

el<<e2

自左至右

左移和右移

6

< <= > >=

el<e2

自左至右

关系运算(比较)

7

== !=

el==e2

自左至右

等于和不等于比较

8

&

e1&e2

自左至右

按位与

9

^

el^e2

自左至右

按位异或

10

|

el|e2

自左至右

按位或

11

&&

e1&&e2

自左至右

逻辑与(并且)

12

||

el||e2

自左至右

逻辑或(或者)

13

?:

el?e2:e3

自右至左

条件运算

14

=

+= -= *=

/= %= >>=

<<= &= ^=

|=

x=e

x+=e

自右至左

赋值运算

复合赋值运算

15

,

el,e2

自左至右

顺序求值运算

注:由于括号的优先级最高,当表达式比较复杂时,使用括号进行结合运算,会优先运算括号内的算式

2.4.1选择控制语句
1、if语句形式:
1)if(条件表达式)
{//复合语句:若干条语句的集合
语句1;

语句2;

}

如果条件成立,则执行{}里的所有语句;条件不成立,则不执行

例20:
#include<stdio.h>
int main()
{
int a=10;
if(a>5)
{
printf("a>5\n");
}
return 0;
}
2)if(条件表达式)
{
}

else

{

}

if、else语句的作用:如果if的条件成立,则执行if后面{}里的语句,否则执行else后面{}里的语句
例21:
#include<stdio.h>
int main()

{

int a=10;
if(a>5)
{
printf("a>5\");
}
else

{
printf("a<=5\n");
}
return 0;

}

注意:if和else之间只能有一条语句或者一个复合语句,否则编译会出错
例22:
if()
语句1;
语句2;
else
语句3;
语句4;
//错误:if和else之间只能有一条语句,如果有多条语句,则加{}
例23:
if()

{

语句1;

语句2;

}
else

{
语句3;

语句4;

}
//正确
3)
if(条件表达式1)
{
}
else if(条件表达式2)

{

}

else if(条件表达式3)
{
}

else

{
}
程序运行时,从上往下依次判断条件表达式,成立则执行对应的复合语句,后边的条件表达式便不再判断,因为各个条件判断是互斥的

例24:
#include <stdio.h>

#pragma warning(disable:4996)
int main(void)

{
char ch;
float score =0;

printf("请输入学生分数:\n");
scanf("%f",&score);

if(score<0 || score >100)
{
printf("输入的信息有误\n");

return 0;
}
else if(score < 60)
{
ch = ‘E’;

}
else if(score < 70)

{
ch = ‘D’;

}
else if(score < 80 )

{

ch = ‘C’;
}
else if (score < 90)
{
ch = ‘B’;

}

else
{
ch = ‘A’;
}

printf(“%c\n”,ch);

return 0;
}

2、switch语句
switch(表达式)//表达式只能是字符型或整型的数据
{

case常量表达式1:
语句1;
break;
case常量表达式2:
语句2;
break;
default:

语句3;

break;
}
注意:break的使用

例25:
#include <stdio.h>
int main(int argc,char *argv[])

{

int n;
printf(“请输入一个1~7的数字\n”);

scanf_s("%d\n",&n);
switch(n)
{
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf(星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf(星期五\n");

break;
case 6:
printf(星期六\n");

break;
case 7:

printf(星期七\n");

break;
default:

printf(“输入有误,请输入1~7的数字\n”);

break;

}

2.4.2循环控制语句
l、for循环
for(表达式1;条件表达式2;表达式3)
{∥循环体

}

第一次进入循环时执行表达式1且只执行一次;然后判断表达式2,条件为真才执行循环体,每次执行循环体前都要判断表达式2 ;每次执行完循环体后,才执行表达式3
例25:for循环求0~100的和

#include <stdio.h>
int main(void)
{
int i;
int sum=0;
for(i=1;i<=100;i++)
{
sum=sum+i;
}
printf("sum=%d\n",sum);
return 0;
}

例26:九九乘法表
#include <stdio.h>
int main(int argc,char *argv[])

{
int i,j;

for(i=1;i<=9;i++)
{
for(j=1;j<=i;j++)
{
printf("%d*%d=%d ",i,j,i*j);
}
printf("\n");
}
return 0;
}
2、while循环
1)形式1:
while(条件表达式)
{//循环体

}

首先判断条件表达式的真假,条件为真则执行循环体,否则不执行循环体
例27:
#include <stdio.h>
int main(void)
{
int i=1;
int sum=0;
while(i<=100)
{
sum=sum+i;
i++;

}

printf("sum=%d\n",sum);
return 0;

}

2)形式2:
do{//循环体
}while(条件表达式):
首先执行循环体,然后判断条件表达式的真假,条件为真则再次执行循环体,否则退出循环

例28:
#include <stdio.h>
int main(void)
{
int i=1;
int sum=0;
do
{
sum=sum+i;
i++
}while(i<=100);
printf("sum=%d\n",sum);
return 0;
}

形式1和形式2的区别:形式1先判断再执行,形式2先执行再判断
注意:break表示跳出循环体,continue表示结束本次循环,进入下一次循环

3、goto
例30:

#include <stdio.h>
int main(int argc,char *argy[])

printf("test000000000000000000\n");

printf("test111111111111111111\n");
goto tmp;
printf("test222222222222222222\n");

printf("test333333333333333333\n");

printf("test444444444444444444\n");

printf("test555555555555555555\n");

tmp;
printf("test666666666666666666\n");

return 0;
}

练习
1.输出0~999的水仙花数
水仙花数算法:一个数=各位的立方和,如153=1*1*1+5*5*5+3*3*3
提示:for循环、取余(%)、取整(/)、()运算符
例:

include<stdio.h>
int main()
{
int i;
int a,b,c;
for(i=0;i<=999;i++)
{
a=i/100;
b=i%100/10;
c=i%10;
if(i==a*a*a+b*b*b+c*c*c)
{
printf(i=%d\n",i);

}

}

return 0;

}

2.任意给出一个日期,判断是这一年的第几天
闰年算法:能被4整除且不能被100整除,或者能被400整除
如:2012/5/10是这一年的第131天
提示:switch循环
例:

#include<stdio.h>
int main()
{
int year,month,day;
int sum;
printf(please input year month day:\n");
scanf_s("%d %d %d",&year,&month,&day);
switch (month)
{
case 1:
sum=day;
break;
case 2:
sum = 31+day;
break;
case 3:
sum = 31+28+day;
break;
case 4:
sum = 31+28+31+day;
break;
case 5:
sum =31+28+31+30+day;
break;
case 6:
sum = 31+28+31+30+31+day;
break;
case 7:
sum=31+28+31+30+31+30+day;
break;
case 8:
sum=31+28+31+30+31+30+31+day;
break;
case 9:
sum = 31+28+31+30+31+30+31+31+day;
break;
case 10:
sum = 31+28+31+30+31+30+31+31+30+day;
break;
case 11:
sum = 31+28+31+30+31+30+31+31+30+31+day;
break;
case 12:
sum = 31+28+31+30+31+30+31+31+30+31+30+day;
break;
default:
sum=0;
printf("month err\n");
break;

}
if((month > 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 = 0)))

{
sum = sum + 1;
printf("%d %d %d 是这一年中的第 %d 天\n",year,month,day);

}
return 0;
}

第3章数组
3.1数组的概念
数组是若干个相同类型的变量在内存中有序存储的集合
int a[l0];∥定义一个整型的数组a,a是数组的名字,数组中有l0个int类型的元素,而且在内存中连续存储
3.2数组的分类
3.2.1按元素的类型分类
1)字符数组:若干个字符型变量的集合
char s[10];
s[0],s[1]...s[9];
2)短整型的数组
short int a[10];

a[0]=4;

a[9]=8;
3)整型的数组
int a[10];

a[0]=3;
4)长整型的数组
long int a[5];
5)浮点型的数组(单、双)
float a[6];

a[4]=3.14f;
double a[8];

a[7]=3.115926;
6)指针数组
char *a[10];
7)结构体数组
struct stu boy[10];
3.2.2按维数分类
一维数组:元素成行排列
int a[30];
二维数组:元素排列有行有列,可以看成由多个相同的一维数组构成
int a[2][30];

三维数组:可以看成由多个相同的二维数组构成
int a[4][2][10];

多维数组以此类推
int a[5][4][2][10];
3.3数组的定义
3.3.1一维数组的定义
格式:
数据类型   数组名    [数组元素个数];
char       b         [5];//定义由5个char类型变量构成的数组b
5个变量分别是b[0],b[1],b[2],b[3],b[4];

在数组定义时可以不给出数组元素的个数,根据初始化的个数确定数组的大小
例1:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a[]={1,2,3,4,5};
printf("%d\n",sizeof(a));
return 0;
}
3.3.2二维数组的定义
格式:
数据类型    数组名     [行的个数][列的个数];
int         a          [4][5];//定义了20个int类型的变量,分别是
a[0][0],a[0][1],a[0][2],a[0][3],a[0][4],
a[1][0],a[1][1],a[1][2],a[1][3],a[1][4],
a[2][0],a[2][1],a[2][2],a[2][3],a[2][4],
a[3][0],a[3][1],a[3][2],a[3][3],a[3][4];
多维数组的格式依次类推
int a[3][4][5];
扩展:

定义二维数组时,可以不给出行数,但必须给出列数,二维数组的大小根据初始化的行数确定
例2:
#include <stdio.h>
int main(int argc,char *argv[])

{
int a[][3]={
{1,2,3},
{4,5,6},
{7,8,9},
{10,11,12}
};
printf("%d\n",sizeof(a));
return 0;
}
3.4数组的初始化
3.4.1一维数组的初始化
a、全部初始化
int a[5]={2,4,7,8,5};//a[0]=2;a[1]=4;a[2]=7;a[3]=8;a[4]=5;
b、部分初始化:初始化时没有赋值的元素补0
int a[5]={2,4,3}; //a[0]=2;a[1]=4;a[2]=3;a[3]=0;a[4]=0;
注意:只能省略后面元素不初始化,中间元素初始化时不能省略前面元素
例3:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a[5]={2,3,5};
int i;
for(i=0;i<5;i++)

{
printf("a[%d]=%d\n",i,a[i]);
}
return 0;
}
3.4.2二维数组的定义并初始化
按行初始化:
a、全部初始化
int a[2][2]={{1,2},{4,5}};//a[0][0]=1;a[0][1]=2;a[1][0]=4,a[1][1]=5;
b、部分初始化
int a[3][3]={{1,2},{1}};//a[0][0]=1;a[0][1]=2; a[0][2]=0;
逐个初始化:
a、全部初始化:
int a[2][3]={2,5,4,2,3,4};
b、部分初始化:
int a[2][3]={3,5,6,8};
3.5数组元素的引用方法
3.5.1一维数组元素的引用方法
数组名[下标];//下标代表数组元素在数组中的位置
int a[5];
a[0],a[1],a[2],a[3],a[4];
3.5.2二维数组元素的引用方法
数组名[行下标][列下标];
int a[4][5];
a[0][0],a[0][1],a[0][2],a[0][3],a[0][4],
a[1][0],a[1][1],a[1][2],a[1][3],a[1][4],
a[2][0],a[2][1],a[2][2],a[2][3],a[2][4],
a[3][0],a[3][1],a[3][2],a[3][3],a[3][4];

例4:
#include <stdio.h>
int main(int argc,char *argv[])
{
int b[3][4]={11,12,13,14,15,16,17,18,19};
int i,j;
for(i=0;i<3;i++)//遍历所有行
{
for(j=0;j<4;j++)//遍历一行的所有列
{
printf("b[%d][%d]=%d",i,j,b[i][j]);
}

}
return 0;
}
3.5.3字符数组
char c1[]={‘c’,‘ ‘,‘p’,‘r’,‘o’,‘g’};
char c2[]=“c pro”;
char a[][5]={
{‘B’,’A’,’S’,’I’,’C’},
{‘d’,’B’,’A’,’S,’E’}
};
char a[][6]={“hello”,“world”};
>字符数组的引用
1.使用字符串赋值比使用字符逐个赋值要多占1个字节,用于存放字符串结束标志’\0’;
2.上面的数组c2在内存中的实际存放情况为:

‘a’

‘ ‘

‘p’

‘r’

‘o’

‘g’

‘\0’

注:’\0’是c编译系统自动添加的,使字符数组的输入输出变得方便
例5:
int main()

{
char str[15];
printf("input string:\n");
scanf_s("%s",str);
printf("output:%s\n",str);
return 0;
}

练习:
1、任意给出一个日期,判断是这一年的第几天
闰年算法:能被4整除且不能被100整除,或者能被400整除
提示:使用数组,将每月的天数放在一个数组中
#include<stdio.h>
int main()
{
int year,month,day;
int i,sum=0;
int buf[12]={31,28,31,30,31,30,31,31,30,31,30,31};
printf(“please input year month day\n”);
scanf_s("%d %d %d",&year,&month,&day);
for(i=0;i < month - 1;i++)

{
sum = sum + buf[i];

}

sum = sum + day;

if ((month > 2) && (((year % 4 == 0) && (year % 100 !=0)) || (year % 400 == 0)))

{
sum = sum + 1;
}
printf("sum=%d\n”,sum);
return 0;
}
2、打字游戏
1)随机函数
A.srand(unsigned)time(NULL);以当前时间为准,设置随机种子
注意:此函数在每次开始游戏后调用一次即可
B.ch=rand();
注意:rand()函数每调用一次,产生一个随机数字
2)获得键值函数
ch = getch();无需按下回车,可直接获得键盘按下的键值
3)时间函数
start_time=time(NULL);
end_time=time(NULL);
4)system("cls");//清空屏幕

第4章函数
4.1函数的概念
函数是c语言的功能单位,实现一个功能可以封装一个函数来实现,以功能为目的定义函数的参数和返回值
4.2函数的分类
1)从定义角度分类,即函数来源
1.库函数(c库实现)
2.自定义函数(个人实现)
3.系统调用(操作系统实现)
2)从参数角度分类
1.有参函数:参数个数和类型不定,完全取决于函数的功能
int fun(int a,float b,double c)
{
}
int max(int x,int y)
{
}
2.无参函数:没有参数,形参列表里可以写void或空白不写
int fun(void)
{
}
int fun()

{
}
3)从返回值角度分类
1.有返回值的函数:定义函数时必须写有返回值类型,在函数体里必须有return,如果没有返回值类型,则默认返回整型数据
例1:
char fun()//定义一个返回字符型数据的函数
{
char b=‘a’;
return b;
}
例2:
fun()
{
return 1;//返回值类型省略,默认返回整型数据
}
2.无返回值的函数:定义函数时,函数名前加void修饰
void fun(形参列表)
{

;

;
}//函数不需要return,但想要结束函数返回到被调用的地方,则写return;或者不写即可

例3:
#include <stdio.h>
int max(int x,int y)
{
int z;
if(x>y)
z=x;

else
z=y;
return z;
}
void help(void)
{
printf("**************************\n");
printf("*********帮助信息*********\n");
printf("**************************\n");
}
in main(int argc,char *argv[])
{
int num;
help();
num=max(10,10+5);
printf("num=%d\n",num);
return 0;
}
4.3函数的定义
1、函数的定义方法
返回值类型  函数名(形参列表)
{//函数体:函数的功能在函数体里实现
}
例4:
int max(int x,int y)
{
int z;
if(x>y)
z=x;
else
z=y;
return z;

}
注:1)形参必须写有类型,而且形参之间用逗号分隔
2)函数的定义不能嵌套,即不能在一个函数体里定义另一个函数,所有函数的定义是平行的
例5:
void fun(void)
{

;
void fun2(void)
{

;
}
}//错误

例6:
void fun(void)

{

;

}
void fun2(void)

{

;

}//正确
注:1)一个程序中的函数只能定义一次
2)函数命名时要求见名知意,符合c语言的命名规则
4.4函数的声明
1、概念:对于已经定义的函数进行说明,函数多次声明
2、函数声明的原因:某些情况下,函数不进行声明,编译器从上往下编译时可能不认识这个函数
3、函数声明的情况:
1、主调函数和被调函数在同一个.c文件中
1)被调函数在上,主调函数在下
例7:

#include <stdio.h>
void fun(void)
{
printf("hello world\n");
}
int main()
{
fun();

return 0;
}//fun()函数不需要声明
2)被调函数在下,主调函数在上
例8:

#include <stdio.h>

void fun(void);
int main()
{
fun();

return 0;
}
void fun(void)
{
printf("hello world\n");
}//fun()函数需要声明

2、主调函数和被调函数不在同一个.c文件中时必须声明

声明的方法:
1)直接声明法:将被调用的函数的第一行copy过去,后面加分号
例9:

mian.c:

#include <stdio.h>
void fun(void);
int main()
{
fun();

return 0;
}

fun.c:
void fun(void)
{
printf("hello world\n");

}
2)间接声明法
将函数的声明放在头文件中,.c程序包含头文件即可
例10:
fun.c:
void fun(void)
{
printf("hello world\n");
}
fun.h:
extern void fun(void);

main.c:

#include <stdio.h>

#include”a.h"
int main()
{
fun();

return 0;
}
4.5函数的调用
函数的调用方法:
变量=函数名(实参列表);//有返回值的
函数名(实参列表);//无返回值的
1、返回值的有无
1).有返回值的函数根据返回值的类型,需要在主调函数中定义一个对应类型的变量用来接收返回值
例11:
int max(intx,inty)//x、y形参,是个变量
{
}
int main()
{
int num;//需要定义一个num接收max()函数的返回值
num=max(4,8);//4和8是实参

return 0;

}
2).没有返回值的函数不需要接收返回值
例12:

#include <stdio.h>
void fun(void)
{
printf("hello world\n");
}
int main()
{
fun();

return 0;
}

2、形参的有无

函数名(实参列表);//有形参的

函数名();//无形参的
注意:形参是被调函数的局部变量,实参可以是变量或者表达式
4.6函数总结
定义函数时,函数的参数和返回值完全取决于函数的功能
使用函数的好处:
1、定义一次,可以多次调用,减少代码的冗余度
2、使代码模块化,方便调试程序,而且阅读方便

4.7变量的存储类别
4.7.2普通的全局变量
概念:在函数外部定义的变量
int num=100;//num是一个全局变量
int main()

{
return 0;
}
作用范围:程序的所有地方,但使用前需要声明

声明方法:extern int num;
注意:声明时不要赋值
生命周期:程序运行的整个过程
注意:定义普通全局变量时,如果不赋初值,它的值默认为0
4.7.3静态全局变量   static
概念:定义全局变量时,前面用static修饰
static int num=100;//num是一个静态全局变量
int main()
{
return 0;
}
作用范围:只在它定义的.c(源文件)中有效
生命周期:程序的整个运行过程
注意:定义静态全局变量时,如果不赋初值,它的值默认为0
4.7.4普通的局部变量
概念:在函数内或者在复合语句中定义的变量
int main()
{
int num;//局部变量
{
int a;//局部变量
}

}
作用范围:在函数内定义的变量,在函数中有效;在复合语句中定义的变量,在复合语句中有效

生命周期:在函数调用之前,局部变量不占用空间;在调用函数时,才为局部变量开辟空间赋初值;在函数调用结束时,局部变量占用的空间就释放。在复合语句中定义的局部变量也一样
#include <stdio.h>
void fun()
{
int num=3;
num++;
printf("num=%d\n",num);
}
int main()
{
fun();
fun();
fun();
return O;
}
4.7.5静态的局部变量
概念:定义局部变量时,前面加static修饰
作用范围:在函数内定义的变量,在函数中有效;在复合语句中定义的变量,在复合语句中有效
生命周期:第一次调用函数时,开辟空间赋初值,函数结束后也不释放;之后再次调用函数时,不再为其开辟空间,也不赋初值,使用的静态局部变量不变
#include <stdio.h>

void fun()
{
static int num=3;
num++;
printf("num=%d\n",num);
}
int main()
{
fun();
fun();
fun();
}
注意:
1、定义普通局部变量时,如果不赋初值,它的值是随机的
2、定义静态局部变量时,如果不赋初值,它的值是0
3、普通全局变量和静态全局变量如果不赋初值,它的值为0
4.7.6外部函数
定义的普通函数都是外部函数,即函数可以在程序的任何一个文件中调用
4.7.7内部函数
返回值类型前面加static修饰这的函数被称为内部函数
static 限定函数的作用范围:仅在定义的.c中有效
内部函数和外部函数的区别:
外部函数在所有地方都可以调用;内部函数只能在所定义的.c中有效
变量存储类别扩展:
在相同作用范围内,变量不能重名;作用范围不同的可以重名

在局部范围内,重名的全局变量不起作用(就近原则)

第5章预处理、动态库、静态库
5.1c语言编译过程
预处理步骤:

1、预编译
将.c中的头文件进行宏展开,生成.i文件
2、编译
将预处理之后的.i文件生成.s汇编文件
3、汇编
将.s汇编文件生成.o目标文件
4、链接
将.o文件链接生成目标文件
Linux下GCC编译器编译过程
gcc  -E  hello.c  -o  hello.i  1、预处理
gcc  -S  hello.i  -o  hello.s  2、编译
gcc  -c  hello.s  -o hello.o  3、汇编
gcc  hello.o -o  hello_elf    4、链接
5.2 include
#include<>//用尖括号包含头文件,在系统指定的路径下找头文件
#include””//用双引号包含头文件,先在当前目录下寻找头文件;如果找不到,再到系统指定的路径下寻找
注意:include经常用来包含头文件,可以包含.c文件,但是一般不包含。因为include包含的文件会在预编译被宏展开,如果一个.c文件被包含多次,宏展开多次,会导致函数重复定义,所以不要包含.c文件
注意:预处理只是对include等预处理操作进行处理并不会进行语法检查,这个阶段有语法错误也不会报错,第二个阶段即编译阶段才进行语法检查。
例1:
main.c:
#include"max.h"
int main(int argc,char *argv[])

{
int num;
num = max(10,20);
return 0;

}
max.h:
int max(int x,int y);
编译:gcc-E main.c-o main.i
5.3 define
用define进行宏定义,宏是在预编译阶段进行替换
1、不带参宏
#define   PI   3.14
//在预编译阶段如果代码中出现PI就用3.14去替换
宏的好处:只要修改宏定义,其他代码在预编译阶段就会重新替换

作用范围:从定义的地方到本文件末尾
注意:宏定义后边不要加分号
例2:

#include<stdio.h>
#define   PI   3.1415926
int main()

{
double f;
f=PI;
printf("%lf\n",f);
return 0;

}
如果想要在中间终止宏的作用,就使用undef进行终止
#undef   PI//终止PI的作用
例3:

#include<stdio.h>
#define   PI   3.1415926
int main()

{
printf("%lf\n",PI);
#undef   PI
#define   PI   3.14

printf(“%lf\n”,PI);

return 0;

}
2、带参宏
#define   S(a,b)    a*b
注意:带参宏的形参a和b没有类型名,S(2,4)在预处理阶段使用实参替换字符形参,其他字符保留,即2*4
例4:

#include<stdio.h>
#define   S(a,b)    a*b
int main(int argc,char *argv[])

{
int num;
num = S(2,4);

printf(“%d\n”,num);
return 0;
}

注意:带参宏是在预处理阶段进行替换
S(2+4,3)被替换成2+4*3
解决歧义的方法:#define   S(a,b)    (a)*(b)
例5:

#include<stdio.h>
#define   S(a,b)    (a)*(b)
int main(int argc,char *argv[])

{
int num;
num=S(2+3,5);//(2+3)*(5)

printf(“%d\n”,num);
return 0;

}
3、带参宏和带参函数的区别
带参宏被调用多少次就会展开多少次,执行代码时没有函数调用的过程,不需要压栈弹栈,所以带参宏会浪费空间。因为被展开多次,所以节省时间。
带参函数的代码只有二份,存在代码段,调用时去代码段取指令,且需要压栈弹栈,有个调用的过程,所以带参函数是浪费时间,但节省空间。并且带参函数的形参是有类型的,而带参宏的形参没有类型名
5.4选择性编译

1、
#ifdef   AAA
代码段一
#else
代码段二
#endif
如果当前.c 文件中#ifdef上面的代码定义过AAA就编译代码段一,否则编译#else下面的代码段二;
注意和if/else语句的区别:if/else语句都会被编译,通过条件选择性执行代码,而选择性编译#ifdef/#else/#endif只编译一段代码

例6:

#include<stdio.h>
#define   AAA
int main(int argc,char *argv[])
{
#ifdef   AAA
printf("hello world\n");
#else
printf("hello China\n");
#endif
return 0;

}
2、
#ifndef   AAA
代码段一
#else
代码段二
#endif
//与第一种互补,经常用在防止头文件重复包含
3、
#if表达式
程序段一
#else
程序段二
#endif
如果表达式为真,编译第一段代码,否则编译第二段代码
选择性编译都是在预编译阶段进行的
5.5静态库
一:动态编译
动态编译使用的是动态库文件进行编译,默认使用的是动态编译方法
gcc  -hello.c  -o  hello
二:静态编译
静态编译使用的静态库文件进行编译
gcc  -static  hello.c  -o  hello
三:静态编译和动态编译区别
1、使用的库文件的格式不一样,动态编译使用动态库,静态编译使用静态库
注意:1)静态编译要把静态库文件打包编译到可执行程序中,包含编译文件和静态库,其大小包含编译文件的大小和静态库的大小
2)动态编译不会把动态库文件打包编译到可执行程序中,它只是编译链接关系
例7:
mytest.c:
#include <stdio.h>
#include "mylib.h"
int main(int argc,char *argv[])

{
int a=10,b=20,max_num,min_num;
max_num=max(a,b);
min_num=min(a,b);
printf("max_num=%d\n",max_num);
printf("min_num=%d\n",min_num);
return 0;

}
mylib.c:
int max(int x,int y)

{
return (x>y)?x:y;

}
int min(int x,int y)

{
return (x<y)?x:y;

}
mylib.h:
extern int max(int x,int y);
extern int min(int x,int y);
制作静态库:
gcc -c mylib.c -o mylib.o
ar rc libtestlib.a mylib.o
注意:静态库命名时必须以lib开头以.a结尾,中间的testlib是自定义的名字
编译程序:
方法1:
gcc  -static  mytest.c  libtestlib.a  -o  mytest
方法2:可以指定头文件及库文件的路径,如libtestlib.a mylib.h 移动到/home/edu下
mv libtestlib.a mylib.h /home/edu
编译程序命令:
gcc  -static  mytest.c-o mytest  -L/home/edu  -ltestlib  -I/home/edu
注意:-L是指定库文件的路径
-l指定找哪个库,指定的只要库文件名1ib后面到.a前面的部分,即自定义的名字
-I指定头文件的路径
方法3:
可以将库文件及头文件存放到系统默认指定的路径下
库文件默认路径是 /1ib 或者是/usr/lib
头文件默认路径是 usr/include
sudo  mv libtestlib.a  /usr/lib
sudo mv mylib.h  /usr/include

sudo的作用是增加权限
编译程序的命令gcc  -static  mytest.c-o mytest  -ltestlib
5.6动态库
制作动态链接库:
gcc  -shared  mylib.c  -o  libtestlib.so
//使用gcc编译、制作动态链接库
动态链接库的使用:
方法1:库函数、头文件均在当前目录下
gcc  mytest.c libtestlib.so  -o  mytest
exportLD_LIBRARY_PATH=./:$LD_LIBRARY_PATH./mytest
方法2:库函数、头文件假设在/opt目录
gcc mytest.c -o mytest -L/home/teacher -ltestlib -I/home/teacher
编译通过,运行时出错,编译时找到了库函数,但链接时找不到库,执行以下操作,把当前目录加入搜索路径
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
#./mytest可找到动态链接库
方法3:库函数、头文件均在系统路径下
cp libtestlib.so/usr/lib
cp mylib.h  /usr/include
gcc mytest.c -o mytest -ltestlib
#./mytest
问题: 前面的静态库也是放在/usb/lib下,那么链接的到底是动态库还是静态库呢?
当静态库与动态库重名时,系统会优先链接动态库,或者我们可以加入-static指定使用静态库

第6章指针
6.1指针
6.1.1内存的分区:

存储器:存储数据器件
外存:外存又叫外部存储器,长期存放数据,掉电不丢失数据
常见的外存设备:硬盘、flash、rom、u盘、光盘、磁带
内存:内存又叫内部存储器,暂时存放数据,掉电丢失数据
常见的内存设备:ram、DDR
内存:物理内存、虚拟内存
物理内存:真实存在的存储设备
虚拟内存:操作系统虚拟出来的内存

Oxffff_ffff

Oxffff_fffe

Oxffff_fffd

0x0000_0003

0x0000_0002

0x0000_0001

0x0000_0000

操作系统会在物理内存和虚拟内存之间做映射

虚拟内存               进程A                 物理内存
在32位操作系统下,每个进程(运行着的程序)的寻址范围是4G,范围是0x00 00 00 00 ~0xff ff ff ff
写应用程序时,所看到的都是虚拟地址

运行程序时,操作系统会将虚拟内存进行分区
1)堆
动态申请内存时,在堆里开辟内存
2)栈
主要存放局部变量
3)静态全局区
1、未初始化的静态全局区
静态变量(定义变量时,前面加static修饰)或全局变量没有初始化的,存放在此区
2、初始化的静态全局区
全局变量、静态变量初始化的,存放在此区
4)代码区
存放程序代码
5)文字常量区
存放常量
6.1.2指针的概念
系统给虚拟内存的每个存储单元分配一个编号,范围是0x00 00 00 00~0xff ff ff ff,这个编号被称为地址,而指针变量的作用是存放一个地址编号

Oxffff_ffff

Oxffff_fffe

Oxffff_fffd

0x0000_0003

0x0000_0002

‘\n’

0x0000_0001

‘a’

0x0000_0000

100

注意:

1: 32位操作系统的地址总线是32位的,所以地址编号是32位,指针变量是32位,无论什么类型的地址,都是存储单元的编号,都是4个字节
2:对应类型的指针变量只能存放对应类型的变量的地址编号
扩展:
字符变量char ch=’b’;ch占1个字节,有一个地址编号

整型变量int a=0x12 34 56 78;a占4个字节,它占有4个字节的存储单元,有4个地址编号

0x00002003

0x12

0x00002002

0x34

0x00002001

0x56

0x00002000

0x78

0x00001fff

‘b’

6.1.3指针变量的定义方法
1.简单的指针变量
数据类型* 指针变量名;
int *p;//定义一个指针变量p
定义指针变量时,*是用来修饰变量的,说明变量p是个指针变量
2.关于指针的运算符
&+变量名表示取地址     *+变量名表示取值
例1:
int a=0x12 34 ab cd;
int *p;//定义指针变量时,*代表修饰的意思,说明变量p是个指针变量
p=&a;//把a的地址给p赋值,&是取地址符,
p保存a的地址,也可以说p指向a
p和a的关系分析:a的值是0xl234abcd,假如a的地址是:0xbf e8 98 68
.

.

.

0x12

int a=0x1234abcd;

0x34

0xab

0xcd

.

.

.

.

0xbf

int *p;

0xe8

0x98

0x68

.

.

.

int num;

num= *p;

分析:
1、调用*p时, * 代表取值的意思, *p就相当于p指向的变量,即a。
2、故num=*p;和num=a;的效果是一样的。
3、所以num的值为0x1234abcd。
扩展:如果在一行中定义多个指针变量,每个变量前都需要加*来修饰
int *p,*q;//定义两个整型指针变量p和q
int *p,q;//定义一个整型指针变量p和整型变量q
例2:

#include<stdio.h>
int main()

{
int a=100,b=200;
int *p_1,*p_2=&b;//表示该变量的类型是一个指针变量,指针变量名是p_1而不是*p_1
//p_1在定义时没有赋初值,p_2赋初值
p_1=&a;//p_1先定义后赋值
printf("%d\n",a);
printf("%d\n",*p_1);
printf("%d\n",b);
printf("%d\n",*p_2);
return 0;

}
注意:

定义p_1时,因为p_1是个局部变量,局部变量没有赋初值,它的值是随机的,p_1指向不定,所以p_1是个野指针
3.指针大小
例3:在32位操作系统下,所有类型的指针都是4个字节
#include <stdio.h>
int main(int argc,char *argv[])

{
char *p1;
short int *p2;
int *p3;
long int*p4;
float *p5;
double *p6;
printf("%d\n",sizeof(p1));
printf("%d\n",sizeof(p2));
printf("%d\n",sizeof(p3));
printf("%d\n",sizeof(p4));
printf("%d\n",sizeof(p5));
printf("%d\n",sizeof(p6));
return 0;

}
例4:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a=0x1234abcd;
int *P;
p=&a;
printf("&a=%p\n",&a);
printf("p=%p\n",p);
return 0;

}
.

.

.

0x12

int a=0x1234abcd;

0xbfe89868

0x34

Oxab

0xcd

.

.

.

.

Oxbf

int *p;

p=&a;

Oxe8

0x98

0x68

.

.

.

6.1.4指针的分类
按指针变量指向的数据类型来分
1:字符指针
字符型数据的地址
char *p;//定义一个字符指针变量p,只能存放字符型数据的地址编号
char ch;
p = &ch;
2:短整型指针
short int *p;//定义一个短整型的指针变量p,只能存放短整型变量的地址编号
short int a;
p = &a;
3:整型指针
int *p;//定义一个整型的指针变量p,只能存放整型变量的地址编号
int a;
p = &a;
注:多字节变量占多个存储单元,每个存储单元都有地址编号,c语言规定存储单元最小的编号是多字节变量的地址编号。
4:长整型指针
long int *p;//定义一个长整型的指针变量p,只能存放长整型变量的地址编号
long int a;
p = &a;
5:f1oat型的指针
float *p;//定义一个float型的指针变量p,只能存放float型变量的地址编号
float a;
p = &a;
6:double型的指针
double *p;//定义一个double型的指针变量p,只能存放double型变量的地址编号
double a;
p = &a;
7:函数指针
8:结构体指针
9:指针的指针
10:数组指针
11:通用指针
void *p;
总结:无论什么类型的指针变量,在32位操作系统下,都是4个字节
指针变量只能存放对应类型的变量的地址编号
6.1.5指针和变量的关系
指针可以存放变量的地址编号
int a=100;
int *p;
p= &a;
程序中引用变量的方法:
1:直接通过变量的名称
int a;
a=100;
2:可以通过指针变量来引用变量
int *p;//在定义的时候,*不是取值的意思,而是修饰的意思,修饰p是个指针变量
p= &a;//取a的地址给p赋值,p保存a的地址,也可以说p指向a
*p=100;//在调用的时候*是取值的意思,*+指针变量等价于指针指向的变量
注:指针变量在定义时可以初始化
int a;
int *p= &a;//用a的地址给p赋值,因为p是指针变量
总结:1、指针就是用来存放变量地址的
2、*+指针变量就相当于指针指向的变量
例5:
#include <stdio.h>
int main()

{
int *p1,*p2,temp,a,b;
p1= &a;
p2= &b;
printf("请输入:a b的值:\n");
scanf_s("%d %d",p1,p2);//给p1和p2指向的变量赋值
temp= *pl;//用p1指向的变量(a)给temp赋值
*p1 = *p2;//用p2指向的变量(b)给p1指向的变量(a)赋值
*p2 = temp;//temp给p2指向的变量(b)赋值
printf("a=%d b=%d\n",a,b);
printf("*p1=%d *p2=%d\n",*p1,*p2);
return 0;

}
运行结果:
输入 100  200
输出结果为:
a=200  b=100
*p1=200  *p2=100
扩展:
对应类型的指针,只能保存对应类型数据的地址,如果想让不同类型的指针相互赋值的时候,需要强制类型转换
void *p;
例6:
#include <stdio.h>
int main()
{
int a=0x12345678,b=0xabcdef66;
char *p1,*p2;
printf("%0x %0x\n",a,b);
p1=(char *)&a;
p2=(char *)&b;
printf("%0x %0x\n",*p1,*p2);
p1++;
p2++;
printf("%0x %0x\n",*p1,*p2);
return 0;

}

int a=0x12 34 56 78

int b=0xab cd ef 66

高地址

0x12

0xab

0x34

0xcd

0x56

Oxef

低地址

p1

0x78

p2

0x66

结果为:
0x78  0x66
0x56  Oxef
注意:
1:*+指针变量取值,取几个字节,由指针类型决定

指针类型为字符指针则取1个字节;指针类型为整型指针则取4个字节;指针类型为double型指针则取8个字节
2:指针++指向下个对应类型的数据
字符指针++,指向下个字符数据,指针存放的地址编号加1
整型指针++,指向下个整型数据,指针存放的地址编号加4
6.1.6指针和数组元素之间的关系
1、
变量存放在内存中,有地址编号;定义的数组是多个相同类型的变量的集合,每个变量都占内存空间,都有地址编号

指针变量当然可以存放数组元素的地址
例7:
int a[5];
//int *p= &a[0];
int *p;
p= &a[0];//指针变量p保存了数组a中第0个元素的地址,即a[0]的地址

0x00002013

a[4]

0x00002012

0x00002011

0x00002010

0x0000200f

a[3]

0x0000200e

0x0000200d

0x0000200c

0x0000200b

a[2]

0x0000200a

0x00002009

0x00002008

0x00002007

a[1]

0x00002006

0x00002005

0x00002004

0x00002003

a[0]

0x00002002

0x00002001

0x00002000

.

.

.

.

.

.

0x00

p

0x00

0x20

0x00

2、数组元素的引用方法
方法1:数组名[下标]
int a[5];
a[2]=100;
方法2:指针名加下标
int a[5];
int *p;
p=a;
p[2]=100;//因为p和a等价
补充:c语言规定:数组的名字就是数组的首地址,即第0个元素的地址,是个常量
注意:p和a的不同,p是指针变量,而a是个常量,所以可以用等号给p赋值,但不能给a赋值
p=&a[3];//正确
a=&a[3];//错误
方法3:通过指针变量运算加取值的方法来引用数组的元素
int a[5];
int *p;
p=a;
*(p+2)=100;//相当于a[2]=100
解释:p是第0个元素的地址,p+2是a[2]这个元素的地址;对第二个元素的地址取值,即a[2]
方法4:通过数组名运算加取值的方法引用数组的元素
int a[5];
*(a+2)=100;//也是可以的,相当于a[2]=100;
注意:a+2是a[2]的地址,这个地方并没有给a赋值。
例8:
#include <stdio.h>
int main(int argc,char *argv[])

{
int a[5]={0,1,2,3,4};
int *p;
p=a;
printf("a[2]=%d\n",a[2]);
printf("p[2]=%d\n",p[2]);
printf("*(p+2)%d\n",*(p+2));
printf("*(a+2)%d\n",*(a+2));
printf("p=%p\n",p);
printf("p+2=%p\n",p+2);
return 0;
}
3、指针的运算
1:指针可以加一个正整数,往下指正整数个它指向的变量,结果还是个地址
前提:指针指向数组时,加一个整数才有意义
例9:
int a[10];
int *p;
p=a;
p+2;//p是a[0]的地址,p+2是&a[2]
假如p保存的地址编号是2000的话,p+2代表的地址编号是2002
例10:
char buf[10];
char *q;
q=buf;
q+2//相当于&buf[2]
假如:q中存放的地址编号是2000的话,q+2代表的地址编号是2002
2:两个相同类型指针可以比较大小
前提:只有两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义,指向前面元素的指针小于指向后面元素的指针
例11:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a[10];
int *p,*q,n;//如果在一行上定义多个指针变量的,每个变量名前面加*
//上边一行定义了两个指针p和q,定义了一个整型的变量n
p=&a[1];
q=&a[6];
if(p<q)
{
printf("p<q\n");
}
else if(p>q)
{
printf("p>q\n");
}
else
{
printf("p == q\n");
return 0;

}
结果是p<q
3.两个相同类型的指针可以做减法
前提:必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义,做减法的结果是两个指针指向的中间有多少个元素
例12:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a[10];
int *p,*q;
p=&a[0];
q=&a[3];
printf("%d\n",q-p);
return 0;
}
结果是3
4:两个相同类型的指针可以相互赋值
注意:只有相同类型的指针才可以相互赋值(void*类型的除外)
int *p;
int *q;
int a;
p=&a;//p保存a的地址,p指向变量a
q=p;//用p给q赋值,q也保存a的地址,即q指向变量a
注意:如果类型不相同的指针要想相互赋值,必须进行强制类型转换
c语言规定数组的名字,就是数组的首地址,就是数组第0个元素的地址
int *p;
int a[10];
//p=a; p=&a[0];这两种赋值方法是等价的
6.1.7指针数组
1、指针和数组的关系:
1:指针可以保存数组元素的地址
2: 指针数组的概念:可以定义一个数组,数组中有若干个相同类型指针变量,这个数组被称为指针数组
2、指针数组的定义方法:
类型说明符*数组名[元素个数];
int *p[5];//定义一个整型的指针数组p,有5个元素p[O]~p[5],
每个元素都是int*类型的变量
int a;
p[0]=&a;
int b[10];
p[1]=&b[5];
//p[2]、*(p+2)是等价的,都是指针数组中的第2个元素
例13:
#include <stdio.h>
int main(int argc,char *argv[])

{
char *name[5]("hello","China","beijing","project","Computer");
int i;
for(i=0;i<5;i++)
{
printf("%s\n",name[i]);
}
return 0;

}

0x00002000

h

e

l

l

o

\0

0x00003000

C

h

i

n

a

\0

0x00004000

b

e

i

j

i

n

g

\0

0x00005000

p

r

o

j

e

c

t

\0

0x00006000

C

o

m

p

u

t

e

r

\0

“hello”“China”“beijing”“project”“Computer”这5个字符串存放在文字常量区
假设:
“hello”首地址是0x00002000
“China”首地址是0x00003000
“beijing”首地址是0x00004000
“project”首地址是0x00005000
“Computer”首地址是0x00006000
则:
name[0]中存放内容为0x00002000
name[1]中存放内容为0x00003000
name[2]中存放内容为0x00004000
name[3]中存放内容为0x00005000
name[4]中存放内容为0x00006000

name[0]

0x00

0x00

0x20

0x00

name[1]

0x00

0x00

0x30

0x00

name[2]

0x00

0x00

0x40

0x00

name[3]

0x00

0x00

0x50

0x00

name[4]

0x00

0x00

0x60

0x00

注意:name[0]name[1]name[2]name[3]name[4] 都是char* 类型的指针变量,分别存放一个地址编号
3、指针数组的分类
字符指针数组char*p[10]、短整型指针数组、整型的指针数组、长整型的指针数组、float型的指针数组、double型的指针数组、结构体指针数组、函数指针数组
6.1.8指针的指针
指针的指针,即指针的地址
定义一个指针变量,指针变量占4个字节,指针变量也有地址编号
例1:
int a=0x12345678;
假如:a的地址是0x00002000
int *p;
p=&a;
则p中存放的是a的地址编号,即0x00002000
因为p占4个字节内存,有地址编号,即指针变量的地址,也就是指针的指针
假如:指针变量p的地址编号是0x00003000,这个地址编号就是指针的地址
我们定义一个变量存放指针变量p的地址编号,这个变量就是指针的指针
int **q;
q=&p;//q保存p的地址,也可以说q指向p
则q保存的是0x00003000

a

0x12

0x00002003

0x34

0x00002002

0x56

0x00002001

0x78

0x00002000

p

0x00

0x00003003

0x00

0x00003002

0x20

0x00003001

0x00

0x00003000

q

0x00

0x00004003

0x00

0x00004002

0x30

0x00004001

0x00

0x00004000

m

0x00

0x00005003

0x00

0x00005002

0x40

0x00005001

0x00

0x00005000

p、q、m都是指针变量,都占4个字节,都存放地址编号,不过变量类型不同
6.1.9字符串和指针
字符串的概念:
字符串就是以’\0’结尾的若干字符的集合,比如“helloworld”
字符串的地址是第一个字符的地址,如字符串“helloworld”的地址,其实是字符串中字符’h’的地址
可以定义一个字符指针变量保存字符串的地址,比如char *s=helloworld”;
字符串的存储形式:数组、文字常量区、堆
1、字符串存放在数组中
其实是在内存(栈、静态全局区)中开辟一段空间存放字符串
char string[100]=“I love C!”;
定义一个字符数组string,用来存放多个字符,并且用”I love C!”给字符数组string初始化,字符串“I love C!”存放在字符数组string中
注:普通全局数组的内存分配在静态全局区
普通局部数组的内存分配在栈区
静态数组(静态全局数组、静态局部数组)的内存分配在静态全局区
2、字符串存放在文字常量区
在文字常量区开辟一段空间存放字符串,将字符串的首地址赋给指针变量
char *str=“I love C!”;
定义一个字符指针变量str,只能存放字符地址编号,“I love C!”中的字符不是存放指针变量str中,指针变量str只是存放字符’I’的地址编号,其实“I love C!”存放在文字常量区
3、字符串存放在堆区
使用malloc等函数在堆区申请空间,将字符串拷贝到堆区
char *str=(char*)malloc(10*sizeof(char))//动态申请了10个字节的存储空间,首地址给str赋值
strcpy(str,"I love C");//将字符串“I love C!”拷贝到str指向的内存里
字符串的可修改性:
字符串内容是否可以修改,取决于字符串存放在哪里
1.存放在数组中的字符串的内容是可修改的
char str[100]="I love C!";
str[0]=‘y’;//正确,可以修改的
注:数组没有用const修饰
2.存放在文字常量区中的内容是不可修改的
char *str="I love C!";
*str=’y’;//错误,’I’存放在文字常量区,不可修改
注:1、str指向文字常量区时,它指向的内存中的内容无法修改
2、str是指针变量,可以指向其它常量,即可以给str重新赋值

3.存放在堆区中的内容是可修改的
char *str=(char*)malloc(10);
strcpy(str,"I love C");
*str=’y’;//正确,因为堆区内容是可修改的
注:1、str指向堆区时,str指向的内存中的内容可以修改
注意:指针变量str指向的内存是否可以修改,取决于str指向的地方
str指向文字常量区时,内存里的内容不可修改
str指向数组(非const修饰)、堆区时,它指向的内存中的内容是可以修改
初始化:
1.字符数组初始化:
char buf aver[20]="hello world";
2.指针变量指向文字常量区进行初始化:
char *buf point="hello world";
3.指针变量指向堆区,堆区存放字符串,不能进行初始化,只能先给指针变量赋值,让指针变量指向堆区,再使用strcpy、scanf等方法把字符串copy到堆区。
char *buf_heap;
buf_heap=(char*)malloc(15);
strcpy(buf_heap,"hello world");
scanf(“%s”,buf_heap);
使用时赋值
1.字符数组:使用scanf或者strcpy
char buf[20]="hello world";
buf="hello kitty";//错误,因为字符数组的名字是个常量,不能用等号给常量赋值
strcpy(buf,"hello kitty");//正确,数组中的内容是可以修改的
scanf("%s",buf);
2.指针变量指向文字常量区
char *buf_point ="hello world";
1)buf_point="hello kitty";//正确,buf_point指向另一个字符串
2)strepy(buf_point,"hello kitty");//错误,这种情况buf_point指向的是文字常量区,内容只读
当指针变量指向文字常量区时,不能通过指针变量修改文字常量区的内容
3.指针变量指向堆区,堆区存放字符串
char *buf_heap;
buf_heap=(char*)malloc(15);
strcpy(buf_heap,"hello world");
scanf_s(“%s”,buf_heap);

字符串和指针总结:
1、指针变量可以指向文字常量区
1)指针变量指向的文字常量区的内容不可修改
2)指针变量的指向可以改变,即可以给指针变量重新赋值

2、指针变量可以指向堆区
1)指针变量指向的堆区的内容可以修改
2)指针变量的指向可以改变,即可以给指针变量重新赋值
3、指针变量也可以指向数组(非const修饰)
例:
char buf[20]="hello world";
char *str=buf;
注:
1.可以修改buf数组的内容
2.可以通过str修改str指向的内存中的内容,即数组buf的内容,如str=“hello kitty”;
3.不能给buf赋值,如buf=“hello kitty”;//错误的,buf是常量
4.可以给str赋值,即str指向别处,如str=“hello kitty”;
6.1.10数组指针
1、二维数组
二维数组有行有列,可以看成多个一维数组的集合,可以认为二维数组的每个元素是一个一维数组
例:
int a[3][5];//定义一个3行5列的二维数组
可以认为二维数组a由3个一维数组构成,每个一维数组都有5个元素
回顾:
数组的名字是数组的首地址,是第0个元素的地址,是个常量,数组名字加1指向下个元素
如二维数组a中,a+1指向下个元素,即下一个一维数组,即下一行
例14:
#include <stdio.h>
int main(int argc,char *argv[])
{
int a[3][5];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);
return 0;
}
2、数组指针的概念:
数组指针是个指针变量,指向一个数组,加1指向下个数组
3、数组指针的定义方法:

数组类型(*指针变量名)[数组元素个数]
int(*p)[5];//定义一个数组指针变量p,数组指针变量p指向有5个元素的整型数组
p+1指向下一个有5个元素的整型数组
例15:
#include<stdio.h>
int main()
{
int a[3][5];//定义一个3行5列的二维数组
int(*p)[5];//定义一个数组指针变量p,p+1指向下一个有5个元素的整型数组
printf("a=%p\n",a);//第0行的行地址
printf("a+1=%p\n",a+1);/第1行的行地址,a和a+1差20个字节
p=a;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);//p+1指向下一个有5个元素的整型数组
return 0;

}
例16:数组指针的用法1
#include<stdio.h>
void fun(int x)
{
int(*p)[5];

p=x;
}
int main()

{
int i,j;
int a[3][5];
fun(a);
for(i=0;i<3;i++)
{
for(j=0;j<5;j++)
{
printf("%d",a[i][j]);
}
printf("\n");
}

return 0;
}
4、各种数组指针的定义:
(1)、一维数组指针加1指向下一个一维数组
int(*p)[5];
配合每行有5个int型元素的二维数组来用
int a[3][5];
int b[4][5];
int c[5][5];
int d[6][5];
……
p=a;
p=b;
p=c;
p=d;
(2)、二维数组指针加1指向下一个二维数组
int(*p)[4][5];
配合有若干个4行5列二维整型数组来用
int a[3][4][5]:
int b[4][4][5];
int c[5][4][5];
int d[6][4][5];
p=a;
p=b;
p=c;
p=d;
例17:
#include <stdio.h>
int main()
{
int a[3][4][5];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);//a和a+1地址编号相差80个字节
int(*p)[4][5];
p=a;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);//p和p+1地址编号相差也80个字节
return 0;
}
5、三维数组指针加1指向下一个三维数组
int(*p)[4][5][6];
//p+1指向下一个由4个5行6列的三维整型数组构成的四维整型数组
int a[3][4][5][6];

a=p;
6、四维数组指针1后指向下个四维数组,以此类推……
7、注意:
容易混淆的概念:
指针数组是个数组,有若干个相同类型的指针构成的集合
int *p[10];
数组p有10个int*类型的指针变量构成,分别是p[0]~p[9]
数组指针是个指针,指向一个数组,加1下一个数组
int(*p)[10];
P是个指针,加1指向下一个一位整型数组
指针的指针:
int **p;//p是指针的指针
int *q;
p=&q;
8、数组名字取地址变成数组指针
一维数组名字取地址变成一维数组指针,加1指向下一个一维数组
int a[10];
a+1指向下一个整型元素,即a[1]的地址
a和a+1相差一个元素,相差4个字节
&a是一个一维整型数组指针,相当于int(*p)[10]
(&a)+1和&a相差一个由10个整型元素构成的数组,即40个字节
例18:
#include <stdio.h>
int main()

{
int a[10];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);
printf("&a=%p\n",&a);
printf("&a+1=%p\n",&a+1);
return 0;

}
a可以看成是int*类型的指针,是a[0]的地址
&a可以看成是数组指针,加1指向下一个由10个整型元素构成的数组
运行程序时, a和&a所代表的地址编号相同,即指向同一个存储单元,但是a和&a的指针类型不同
例19:
int a[4][5];
a+1指向下一个由5个整型元素构成的数组
(&a)+1指向下一个4行5列的二维整型数组,相差80个字节
9、数组名字和指针变量的区别:
int a[5];
int *p;
p=a;
相同点:
a是数组的名字,是a[0]的地址,p=a代表p保存a[0]的地址

不同点:
1、a是常量,而p是变量
可以用等号”=”给p赋值,但是不能用等号”=”给a赋值
2、对a取地址和对p取地址结果不同
a是数组名字,对a取地址结果为数组指针
p是指针变量,对p取地址(&p)结果为指针的指针
例:int a[5]={0,1,2,3,4};

int *p=a;
假如a[0]的地址为0x00002000,p的地址为0x00003000

变量名称

地址编号

a[4]

0x00

0x00002013

0x00

0x00002012

0x00

0x00002011

0x04

0x00002010

a[3]

0x00

0x0000200f

0x00

0x0000200e

0x00

0x0000200d

0x03

0x0000200c

a[2]

0x00

0x0000200b

0x00

0x0000200a

0x00

0x00002009

0x02

0x00002008

a[1]

0x00

0x00002007

0x00

0x00002006

0x00

0x00002005

0x01

0x00002004

a[0]

0x00

0x00002003

0x00

0x00002002

0x00

0x00002001

0x00

0x00002000

变量名称

地址编号

p

0x00

0x00

0x20

0x00

0x00003000

1、&p是指针的指针,即int**类型,结果是0x00003000,&p+1往后指向下一个int*类型的指针,地址编号相差4
2、&a是数组指针,是int(*)[5]类型,结果是0x00002000,&a+1往后指向下一个由5个整型元素构成的数组,地址编号相差20
例20:
#include <stdio.h>
int main(int argc,char *argv[])

{
int a[5];
int *p;
p=a;
printf("a=%p\n",a);
printf("&a=%p\n",&a);
printf("&a+1=%p\n",&a+1);
printf("p=%p\n",p);
printf("&p=%p\n",&p);
printf("&p+1=%p\n",&p+1);
return 0;

}
10、数组指针取*
数组指针取*,并不是取值的意思,而是指针的类型发生变化
一维数组指针取*,结果为它指向的一维数组第0个元素的地址,它们还是指向同一个地方
二维数组指针取*,结果为一维数组指针,它们还是指向同一个地方
三维数组指针取*,结果为二维数组指针,它们还是指向同一个地方
多维以此类推
例21:
#include <stdio.h>
int main()

{
int a[3][5];
int(*p)[5];
p = a;
printf("a=%p\n",a);//a是一维数组指针,指向第0个一维数组,即第0行
printf("*a=%p\n",*a);//*a是第0行第0个元素的地址,即&a[0][0]
printf("*a+1=%p\n",*a+1);//*a+1是第0行第1个元素的地址,即&a[0][1]
printf("p=%p\n",p);//p是一维数组指针,指向第0个一维数组,即第0行
printf("*p=%p\n",*p);//*p是第0行第0个元素的地址,即&a[0][0]
printf("p+1=%p\n",*p+1);//*p+1是第0行第1个元素的地址,即&a[0][1]
return 0;

}
6.1.11指针和函数的关系
6.1.11.1指针作为函数的参数
可以给函数传送一个整型、字符型、浮点型的数据,也可以
给函数传送一个地址
例:
int num;
scanf_s("%d",&num);
函数传参:
(1)、传送数值:
例22:

#include <stdio.h>
void swap(int x,int y)
{
int temp;
temp=x;
x=y;
y=temp;

}
int main()

{
int a=10,b=20;
swap(a,b);
printf("a=%d b=%d\n",a,b);//a=10 b=20

return 0;

}
实参:调用函数时传送的参数。
形参:定义被调函数时,函数名后边括号里的数据
结论:给被调函数传送数值,只能改变被调函数形参的值,不能改变主调函数实参的值
(2)、传送地址:
例23:

#include <stdio.h>
void swap(int *pl,int *p2)
{
int temp;
temp=*pl;
*p1=*p2;//p2指向的变量的值,给p1指向的变量赋值
*p2=temp;
}
int main()
{
int a=10,b=20;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);//结果为a=20 b=10

return 0;

}
结论:调用函数时,传送变量的地址,在被调函数中通过*+指针变量来改变主调函数中的变量的值
例24:

#include <stdio.h>
void swap(int *p1,int *p2)//&a &b

{
int *p;
p=p1;
p1=p2;//p1=&b;让p1指向main函数中的b
p2=p;//p1=&a;让p2指向main函数中a

}//此函数中改变的是p1和p2的指向,并没有给main函数中的a和b赋值
int main()
{
int a=10,b=20;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);//结果为a=10 b=20

return 0;

}
总结:要改变主调函数中变量的值,必须传送变量的地址,而且通过*+地址进行赋值
例25:

#include <stdio.h>
void fun(char *p)
{
p="hello kitty";
}
int main()
{
char *p="hello world";
fun(p);
printf("%s\n",p);//结果为:hello world

return 0;

}
答案分析:在fun函数中改变的是fun函数中的局部变量p,并没有改变main函数中的局部变量p,所以main函数中指针变量p还是指向hello world
例26:

#include <stdio.h>
void fun(char **q)
{
*q="hello kitty";

}
int main()
{
char *p="hello world";
fun(&p);
printf("%s\n",p);//结果为:hello kitty

return 0;
}
总结:要改变主调函数中变量的值,必须传送变量的地址,而且通过*+地址进行赋值,无论这个变量是什么类型的
(3)、传送数组:
给函数传送数组时,无法短时间将数组的内容作为整体传送进去,只能传送数组名进去,即只能传送数组的首地址
例27:传送一维数组的地址

#include <stdio.h>
//void fun(int p[])//形式1
void fun(int *p)//形式2
{
printf("%d\n",p[2]);
printf("%d\n",*(p+3));
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8};
fun(a);
return 0;
}
例28:传送二维数组的地址

#include <stdio.h>
//void fun(int p[][4])//形式1
void fun(int (*p)[4])//形式2
{

printf("%d\n",p[1][4]);
}
int main()
{
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
fun(a);
return 0;
}

例29:传送指针数组

#include <stdio.h>
void fun(char **q)//char *q[]
{
int i;
for(i=0;i<3;i++)
printf("%s\n",q[i]);
}
int main()
{
char *p[3]={"hello","world","kitty"};
fun(p);
return 0;
}
6.1.11.2指针作为函数的返回值
函数可以返回整型数据、字符型数据、浮点型的数据,也可以返回指针数据
例30:

#include <stdio.h>
char *fun()
{
char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);

return 0;

}

总结:返回地址时,地址内存的内容不能释放,如果返回的指针变量指向的内容被释放,返回指针变量无意义
例31:返回静态局部数组的地址

#include <stdio.h>
char *fun()
{
static char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world

return 0;
}
分析:静态数组的内容在函数调用结束后,仍然存在

例32:返回文字常量区的字符串的地址

#include <stdio.h>

char *fun()
{
static char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world

return 0;
}
分析:文字常量区的内容一直存在

例33:返回堆内存的地址

#include <stdio.h>

char *fun()
{

char *str;

str=(char*)malloc(100);
strcpy(str,”hello world”);
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world

free(p);

return 0;
}
分析:堆区的内容一直存在,直到free函数才释放

总结:返回的地址指向的内存中的内容存在,才有意义
6.1.11.3指针保存函数的地址(函数指针)
1、函数指针的概念:
定义的函数在运行程序时,会将函数的指令加载到内存的代码段,所以函数也有起始地址
c语言规定:函数的名字就是函数的首地址,即函数的入口地址
可以定义一个指针变量存放函数的地址,即函数指针变量
2、函数指针的用处:
函数指针用来保存函数的入口地址
在项目开发中,经常需要编写或者调用带函数指针参数的函数
比如Linux系统中创建多线程的函数,它有个参数就是函数指针,接收线程函数的入口地址,即创建线程,创建成功后,新的任务执行线程函数
int pthread_create(pthread_t*thread,const pthread_attr_t*attr.
void *(*start_routine)(void*),void *arg);
void *thread_fun1(void *arg)
{

}
void *thread_fun2(void *arg)

{


}
int main()
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,thread_fun1,NULL);
pthread_create(&tid2,NULL,thread_fun2,NULL);

return 0;

}
3、函数指针变量的定义
返回值类型(*函数指针变量名)(形参列表);
int(*p)(int,int);//定义一个函数指针变量p,必须有一个整型的返回值,有两个整型参数
int max(int x,int y)
{


}

int min(int x,int y)
{


}
可以用函数指针变量p存放相同类型的函数地址
p=max;
p=min;
4、调用函数的方法
1.通过函数的名字进行调用函数(最常用)

#include <stdio.h>
int max(int x,int y)
{

if(x>y)

return x;

else if(x<y)

return y;

else

return x;
}
int main()
{
int num;
num=max(3,5);

printf(“%d”,num);

return 0;

}
2.通过函数指针变量进行调用函数

#include <stdio.h>
int max(int x,int y)
{

if(x>y)

return x;

else if(x<y)

return y;

else

return x;
}
int main()
{
int num;
int (*p)(int,int);
p=max;
num=(*p)(3,5);

printf(“%d”,num);

return 0;
}
5、函数指针数组
概念:由若干个相同类型的函数指针变量构成的集合,在内存中按照连续的顺序进行存储
函数指针数组的定义:
类型名(*数组名[元素个数])(形参列表);
int(*p[5])(int,int);

//定义一个函数指针数组,有5个元素p[0](int,int);~p[4](int,int);,每个函数指针变量指向的函数必须有整型的返回值和两个整型参数
6、函数指针应用举例:
给函数传送实参
#include <stdio.h>
int add(int x,int y)

{
return x+y;
}
int sub(int x,int y)

{
return x-y;

}
int mux(intx,inty)

{
return x*y;
}
int dive(int x,int y)

{
return x/y;

}
int process(int(*p)(int,int),int x,int y)
{
int ret;
ret=(*p)(x,y);
return ret;

}
int main()

{
int num;
num=process(add,10,20);
printf("num=%d\n",num);
num=process(mux,10,20);
printf("num=%d\n",num);
return 0;

}
6.1.12经常容易混淆的指针概念
第一组:
1、int *a[10];//定义一个指针数组,有10个整型的指针变量,即a[0];~a[9];
2、int(*a)[10];//定义一个一维数组指针变量,指向一个二维数组,加1指向下一个一维数组
3、int **p;//定义一个指针的指针,保存指针变量的地址
常见用法1:
int **p;
int *q;
p=&q;
常见用法2:
int **p;
int *q[10];
//分析:q是指针数组的名字,是指针数组的首地址,是q[0]的地址,q和q[0]地址都是int**类型的
p=&q[0];//等价于p=q;

例34:

include <stdio.h>
void fun(char *p[3])
{
int i;
for(i=0;i<3;i++)

{
printf("%s\n",p[i]);
}
}
int main()
{
char *q[3]={"hello","world","China"};
fun(q);
return 0;
}
第二组:
l、int *f(void);
注意:*f没有用括号括起来,是函数的声明,函数返回值为int*类型
2、int (*f)(void);
注意:用括号括起来,说明f是个函数指针变量,存放函数的地址,它指向的函数必须有int型的返回值且没有形参
6.1.13特殊指针
l、
char* 类型的指针变量,只能保存char型数据的地址
int* 类型的指针变量,只能保存int型数据的地址
float* 类型的指针变量,只能保存float型数据的地址
void* 是空类型指针变量,void* 是通用指针,任何类型数据的地址都可以给void* 类型的指针变量赋值

例:
int *p;
void *q;
q=p;//正确,不用强制类型转换
例:
void memset(void *s,int c,size_t n);
//函数功能:将s指向内存中的前个字节给c赋值,memset函数可以设置字符数组、整型数组、浮点型数组的数据,所以第一个参数必须是通用指针;它的返回值是s指向内存中的首地址,可能是不同类型的地址数据,所以返回值也是通用指针
注意:void*类型的指针变量在32位操作系统下占4个字节
2、NULL是空指针:
char *p=NULL;
//可以认为p不指向任何地方,也可以认为p指向内存编号为0的存储单位,即p的4个字节存放0x00 00 00 00;
NULL给指针变量初始化

main函数传参:
例35:
#include <stdio.h>
int main(int argc,char *argv[])
{
int i;
printf("argc=%d\n",argc);
for(i=0;i<argc;i++)
{
printf("argv[%d]=%s\n",i,argv[i]);
}
return 0;
}

第7章动态内存申请
7.1动态分配内存的概述
    数组一章介绍数组的长度是预先定义的,程序运行过程中固定不变,但在编程过程中所需的内存空间取决于实际输入的数据,而无法预先确定。为了解决上述问题,c语言提供一些内存管理函数,可以按照需要动态地分配内存空间,也可以将不再使用的空间回收再次利用
7.2静态分配、动态分配
静态分配
1、在程序编译或运行过程中,按照预先规定大小分配内存空间的分配方式
2、必须预先知道所需内存空间的大小
3、分配在栈区或全局变量区,一般以数组的形式进行分配
4、按计划分配
动态分配
1、在程序运行过程中,根据需要自由分配所需空间
2、按需分配
3、分配在堆区,一般使用特定的函数进行分配
7.3动态分配函数
stdlib.h
1、malloc函数
函数原型:  void *malloc(unsigned int size);
功能说明:
    在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。函数原型返回void*指针变量,使用时必须作相应的强制类型转换,分配的内存空间内容不确定,一般使用memset初始化
返回值:分配空间的起始地址(分配成功)
        NULL               (分配失败)
注意
1、在调用malloc函数后,需要判断申请内存是否成功
2、如果多次调用malloc函数,第1次申请和第2次申请的内存不一定是连续的
例:

char *p;
p = (char*)malloc(20);

例1:
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
int i,*array,n;
printf("输入申请的整型数组元素个数\n");
scanf("%d",&n);
array=(int*)malloc(n*sizeof(int));
if(array==NULL)
{
printf("申请内存失败\n");
return 0;
}
memset(array,0,n*sizeof(int));
for(i=0;i<n;i++)
{
array[i]=i;
}
for(i=0;i<n;i++)
{
printf("%d\n",array[i]);
}
free(array);//释放array指向的内存
return 0;

}
2、free函数(释放内存函数)
头文件:#include<stdlib.h>
函数定义:void free((void*ptr)
函数说明:free函数释放ptr指向的内存
注意:ptr指向的内存必须是malloc函数、calloc函数、relloc函数动态申请的内存
例2:
char *p=(char *)malloc(100);
free(p);
注意
(1)调用free函数后,指针变量p没有进行初始化,所以p指向原先动态申请的内存,但内存已经不能使用,所以指针变量p变成野指针
(2)一段动态申请的内存只能调用一次free函数,不能多次调用
3、calloc函数
头文件:#include<stdlib.h>
函数定义:void* calloc(size_t nmemb,size_t size);
size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数的功能:在内存的堆区中申请nmemb个储存空间,每个储存空间的大小为size个字节的连续区域
函数的返回值:
返回申请的内存的首地址(申请成功)
返回NULL(申请失败)
注意:
malloc函数和calloc函数都是用来申请内存的
区别:
1)函数的名字不一样
2)参数的个数不一样
3)malloc函数申请的内存中存放的内容是随机的,而calloc函数申请的内存中存放的内容为0

例3:调用方法
char *p=(char*)calloc(3,100);
在堆区中申请3块存储空间,每块大小为100个字节,即300个字节的连续区域
4、realloc函数(重新申请内存)
    调用malloc函数和calloc函数时,单次申请的内存是连续的,两次申请的两块内存不一定连续。但用malloc函数或calloc函数申请一块内存后,再次申请内存想要两块连续,或者使用malloc函数或calloc函数申请一块内存后,想要释放后边的一部分内存

头文件:#include<stdlib.h>
函数的定义:void *realloc(void *s,unsigned int new_size);
函数功能(在原先s指向的内存基础上重新申请new_size个字节大小的内存):

1) new_size个字节比原先s指向的内存大:如果原先s指向的内存后面有足够空间,就接着申请;如果后面内存不够,则realloc函数会在堆区寻找一个new_size个字节大小的内存进行申请,将原先s指向的内存中的内容copy过去,然后释放原先s指向的内存,最后返回新内存的地址

2) new_size个字节比原先s指向的内存小:释放原先s指向的内存后面的存储空间,保留前面的new_size个字节大小的内存
返回值:新申请内存的首地址
例4:
char *p;
p=(char *)malloc(100);//在100个字节后面追加50个字节
p=(char  *)realloc(p,150);//p指向的内存大小变成150个字节

p=(char *)malloc(100);

p

100个字节的内存

p=(char *)realloc(p,150);
情况1:

p

100个字节的内存

追加50个字节

情况2:

p

100个字节的内存

已经使用了的内存

                               中间不够50个字节

解决方法:realloc函数在堆区申请一个150个字节大小的连续空间,将原来的100个字节copy过去,然后释放原先的内存,最后返回新内存的地址。

p

150个字节的内存

例5:
char *p;
p=(char *)malloc(100);
//重新申请50个字节大小的内存
p=(char*)realloc(p,50);//p指向的内存大小变成50个字节,原先内存的后50个字节的存储空间被释放
注意:malloc函数、calloc函数、realloc函数动态申请的内存只有调用free函数或程序运行结束时才被释放
7.4内存泄露
内存泄露的概念:
申请的内存的首地址无法找到,导致内存无法使用或释放
内存泄露例1:
int main()

{
char *p;
p=(char *)malloc(100);//p指向的内存可以使用
p="hello world";//p指向的内存改变,所以原先申请的100个字节大小的内存无法找到首地址进行使用,则内存泄露
return O;
}
内存泄露例2:
void fun()

{
char *p;
p=(char *)malloc(100);// p指向的内存可以使用
;

;

}
int main()

{
fun();
fun();
return 0;

}
//每次调用fun函数就泄露100个字节大小的内存空间
内存泄露解决方案1:
void fun()

{
char *p;
p=(char *)malloc(100);// p指向的内存可以使用

;

;
free(p);

}
int main()
fun();
fun();
return 0;

}
内存泄露解决方案2:
char *fun()

{
char *p;
p=(char *)malloc(100);// p指向的内存可以使用

;

;
return p;

}
int main()

{
char *q;
q=fun();//可通过指针变量q使用动态申请的100个字节大小的内存
free(q); //需要释放
return 0;

}
总结:申请的内存首地址不可以丢弃,内存不用时需要释放
第8章字符串处理函数
#pragma(编译指示)指令的作用:用于指定计算机或操作系统特定的编译器功能
例:

#pragma warning(disable:4996)在.c文件顶端输入此语句,即向编译器发送指令忽略4996警告,strepy、scanf等一些不安全的标准c库函数在visual studio编译器中就可以进行使用
8.1测量字符串长度函数
头文件:#include<string.h>
函数定义:size_t strlen(const char*s);

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数功能:
测量字符指针变量s指向的字符串中字符的个数,但不包括’\0’
返回值:字符串中字符的个数
例1:
#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)
int main()

{
char str1[20]="hello";
char *str2="hello";
printf("%d\n",sizeof(str1));//20
printf("%d\n",sizeof(str2));//4
printf("%d\n",strlen(str1));//5
printf("%d\n",strlen(str2));//5
return 0;

}
sizeof是关键字,测量数据占用内存空间的大小
如果测量数组名字,则测量数组占用内存空间的大小
如果测量指针变量,则测量指针变量占用内存空间的大小,32位系统的结果是4
strlen是库函数,测量字符指针变量指向的字符串中字符的个数,数组名字也适用
8.2字符串copy函数
头文件:#include<string.h>
函数的声明:char *strcpy(char *dest,const char *src);
函数的说明:
拷贝src指向的字符串到dest指向的内存中,’\0’也会拷贝
函数的返回值:dest指向的内存地址
注意:使用此函数必须保证dest指向的内存空间足够大,否则会出现内存污染

例:

#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)
int main()

{

char str[100]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
strcpy(str,"hello");
printf("%s\n",str);//hello

printf("%s\n",str+6);//aaaaaaaaaaaaaaaaaaaaaaa

return 0;

}

头文件:#include<string.h>
函数的声明:char *strncpy(char *dest,const char *src,size_t n);

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数的说明:
拷贝src指向的字符串前n个字节到dest指向的内存中
返回值:dest指向的内存地址
注意:
1、strncpy不会拷贝’\0’
2、如果n大于src指向的字符串中的字符个数,则在dest指向的内存后面填充n减去strlen(scr)的数量的’\0’
例2:
#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)
int main()

{
char buf[100]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
strncpy(buf,"helloworld",5);
printf("%s\n",buf);

return 0;

}
结果为helloaaaaaaaaaaaaaaaaaaaaaaaa
验证不会拷贝’\0’
例3:
#include<stdio.h>
#include<string.h>

#pragma warning(disable:4996)
int main()
{
int len,i;
char buf[100]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
len=strlen(buf);
strncpy(buf,"helloworld",15);
for(i=0;i<len;i++)
printf("%c\n ",buf[i]);
return 0;
}

注意:使用strcpy函数和strncpy函数时,如果指针变量dest是野指针,则无法拷贝src指向的字符串到dest指向的内存中
8.3字符串追加函数
头文件:#include<string.h>
函数声明:char *strcat(char *dest,const char *src);
函数功能:strcat函数追加src指向的字符串到dest指向的字符串的后面,也会追加’\0’

注意:保证dest指向的内存空间足够大
例4:
#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)
int main()
{
char str[100]="aa\0aaaaaaaaaaaaaaaaa";
char *src ="hello";
strcat(str,src);
printf("%s\n",str);
return 0;

}

结果是aahello,验证会追加’/0’

头文件:#include<string.h>
函数声明:char *strncat(char *dest,const char *src,size_t n);

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数功能:strcat函数追加src指向的字符串前n个字节到dest指向的字符串的后面
注意:如果n大于src指向的字符串中的字符个数,则只将src指向的字符串追加到dest指向的字符串的后面,也会追加’/0’
例5:
#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)
int main()
{
char str[100]="aa\Oaaaaaaaaaaaaaaaaa";
char *src ="hello";
strncat(str,src,3);
printf("%s\n",str);
return 0;

}
//结果是aahel,验证会追加’\0’
8.4字符串比较函数
头文件:#include<string.h>
函数声明:int strcmp(const char *sl,const char *s2);
函数功能:比较s1和s2指向的字符串的大小
比较方法:逐个字符依次比较字符对应的ASCII码表中的值,直到比较出大小才返回整型数据
返回值:
s1指向的字符串大于s2指向的字符串,则返回1
s1指向的字符串小于s2指向的字符串,则返回-1
s1指向的字符串等于s2指向的字符串,则返回0

头文件:#include<string.h>
函数声明:int strncmp(const char *s1,const char *s2,size_t n);

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数功能:比较s1和s2指向的字符串中的前n个字符

比较方法和返回值与strcmp函数相同
例6:
#include <stdio.h>
#include <string.h>

#pragma warning(disable:4996)
int main()

{
char *str1="hello world";
char *str2="hello kitty";
if(strcmp(str1,str2)==0)

{
printf("str1==str2\n");

}
else if(strcmp(str1,str2)>0)

{
printf("str1>str2\n");

}
else

{
printf("str1<str2\n");

}
if(strncmp(str1,str2,5)==0)

{
printf("str1==str2\n");

}
else if(strncmp(str1,str2,5)>0)

{
printf("str1>str2\n");

}
else

{
printf("str1<str2\n");

}
return 0;

}
8.5字符查找函数
头文件:#include<string.h>
函数声明:char *strchr(const char *s,int c);
函数功能:在s指向的字符串中寻找ASCⅡ码表的值是c所对应的字符
注意:如果s指向的字符串中有多个ASCⅡ码表的值是c所对应的字符,则匹配第1个字符
返回值:
寻找成功则返回匹配字符的地址
寻找失败则返回NULL

头文件:#include<string.h>
函数定义:char *strrchr(const char*s,int c);
函数功能:在s指向的字符串中寻找ASCⅡ码表的值是c所对应的字符

注意:如果s指向的字符串中有多个ASCⅡ码表的值是c所对应的字符,则匹配最后1个符号条件的字符
返回值:

寻找成功则返回匹配字符的地址

寻找失败则返回NULL

例:
#include<stdio.h>
#include<string.h>
#pragma warning (disable:4996)
int main()

{
char *str="helloworldhelloworldhelloworld";
char *p;
p = strchr(str,’w’);
if (p == NULL)
{
printf("寻找失败\n");
}

Else

{
printf("p-str=%d\n",p-str);

}
return 0;

}
8.6字符串匹配函数
头文件:#include<string.h>
函数声明:char *strstr(const char *haystack,const char *needle);
函数功能:在haystack指向的字符串中寻找needle指向的字符串

注意:如果haystack指向的字符串中有多个needle指向的字符串,则匹配第1个字符串
返回值:
寻找成功则返回匹配字符串的地址
寻找失败则返回NULL
例7:
#include <string.h>
#include <stdio.h>

#pragma warning (disable:4996)
int main()
{
char str1[100]="jfsdjklsd43$#$53jklj$#$4t5";
char str2[100]="$#$";
char *result;
result=strstr(str1,str2);
printf("%s\n",result);
printf("%d\n",result-str1);
return 0;

}
8.7空间设定函数
头文件:#include<string.h>
函数声明:void *memset(void *ptr,int value,size_t num)

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数功能:将value赋值给ptr指向的内存空间中的num个字节
参数:
ptr:指向需要修改的内存空间
value:赋值给ptr指向的内存空间
num:ptr指向的内存空间中的num个字节
返回值:ptr指向的内存的地址
#include<stdio.h>
#include<string.h>
#pragma warning (disable:4996)
int main()
{
char str[100]="helloworld";
memset(str,’\0’,100);
printf("str=%s\n",str);
memset(str,’a’,5);
printf("str=%s\n",str);
return 0;
}

8.8字符串转换数值
atoi/atol/atof   //字符串转换功能
头文件:#include<stdlib.h>
函数声明:int atoi(const char *nptr);
函数功能:将nptr指向的字符串转换成整型数据
返回值:转换成的整型数据由输入的字符串作为数字解析而生成;如果输入的字符串无法转换成整型数据,则返回0
例8:

#include<stdio.h>
#include<stdlib.h>
#pragma warning (disable:4996)
int main()
{
int num;
num=atoi("123”);

printf(“%d\n”,num);

return 0;

}//num的值是123

头文件:#include<stdlib.h>
函数声明:long atol(const char *nptr);

函数功能:将nptr指向的字符串转换成长整型数据

返回值与atoi的相似

头文件:#include<stdlib.h>
函数声明:double atof(const char *nptr);

函数功能:将nptr指向的字符串转换成浮点型数据

返回值与atoi的相似
8.9字符串切割函数
头文件:#include<string.h>
函数声明:char *strtok(char *str,const char *delim);
函数功能:将与delim指向的字符串中的字符相同的str指向的字符串中的字符替换成’\0’

返回值:切割一次则返回’\0’之前的字符串;切割两次以上则返回上次切割的’\0’和本次切割的’\0’之间的字符串;切割完全则返回NULL

注意:

1、调用strtok函数一次,就只切割一次;第二次切割时将NULL赋值给strtok函数中的第一个参数,则会接着第一次继续切割

2、如果str指向的字符串中有多个连续的delim指向的字符串中的字符,则只将第一个字符替换成’\0’
例9:
#include<stdio.h>
#include<stdlib.h>
#pragma warning (disable:4996)
int main()

{
char str[l00]="xiaoming:21,,,.男.女,北京:haidian";
char *p[7];
int i=0,j;
printf("str=%s\n",str);
p[i]=strtok(str,":,.");
printf("p[%d]=%s\n",i,p[i]);
printf("str=%s\n",str);
while (p[i]!=NULL)
{
i++;
p[i]strtok = (NULL,":,.");
}
for(j=0;j〈 i;j++)
{
printf("p[%d]=%s\n",j,p[j]);
}
printf("p[1]+3=%s\n",p[1]+3);// 结果是”,,.男”
return 0;

}
例10:
以下为我们的手机收到的短信的格式,请利用指针数组与strtok函数对其解析
char msg_src[]="+CMGR:REC UNREAD,+8613466630259,98/10/01,18:22:11+00,ABCdefGHI";
参考以下的函数声明以及参数功能,完成相应的要求
函数声明:int msg_deal(char *msg_src,char *msg_done[],char *str)
参数1:待切割字符串的首地址
参数2:指针数组用于存放切割完字符串的首地址
参数3:切割字符
返回值:切割的字符串总数量
手机号:13466630259
日期:98/10/01
时间:18:22:11
内容:ABCdefGHI
#include<stdio.h>
#include<string.h>
#pragma warning(disable:4996)
int msg_deal(char*msg_src,char*msg_done[],char*str)

{
int i=0;
msg_done[i] = strtok(msg_src,str);
while (msg_done[i]!=NULL)

{
i++;
msg_done[i]=strtok(NULL,str);

}
return i;

}
int main()

{
char str[100]="+CMGR:REC UNREAD,+8613466630259,98/10/01,18:22:11+00, ABCdefGHI”;
char *p[6];
int num,i;
num = msg_deal(str,p,",");
printf(“num=%d\n”,num);
for (i=0;i < num;i++)

{
printf("p[%d]=%s\n",i,p[i]);
}

printf("手机号:%s\n",p[1]+3);
printf("日期:%s\n",p[2]);
*(p[3]+8)=’\0’;
printf("时间:%s\n",p[3]);
printf("内容:%s\n",p[4]);
return 0;
}
8.10格式化字符串操作函数
头文件:#include<stdio.h>

函数声明:int sprintf(char *buf,const char *format,message);

函数功能:给buf指定的内存区域输出信息
例:

#include<stdio.h>
#pragma warning(disable:4996)

int main()

{
char buf[100];
sprintf(buf,"%d:%d:%d",2013,10,1);
printf(“buf=%s\n”,buf);

return 0;

}

头文件:#include<stdio.h>
函数声明:int sscanf(const char *buf,const char *format,message address);

函数功能:从buf指定的内存区域中读入信息
例:

#include<stdio.h>
#pragma warning(disable:4996)

int main()

{

int a,b,c;
sscanf("2013:l0:1","%d:%d:%d",&a,&b,&c);
printf(“%d %d %d\n”,a,b,c);

return 0;

}


sscanf函数的高级用法:
1)跳过数据,如%*s、 %*d等
例12:
#include<stdio.h>

#pragma warning(disable:4996)
int main()

{
char buf[20];
sscanf("1234 5678","%*d %s",buf);//通过空格进行数据分段获取
printf("%s\n",buf);

}
//结果是5678
2)读取指定宽度的数据,如%[width]s、%[width]d等
例13:
#include<stdio.h>

#pragma warning(disable:4996)
int main()

{
char buf[20];
sscanf("12345678","%4s",buf);//获取4个字节大小的字符串,并赋值给buf
printf("%s\n",buf);//1234
}
3)支持集合操作,但仅限于获取字符串(ASCII表顺序)

一、%[a-z] 表示依照顺序检索并匹配字符串中a到z的字符,检索到范围外的字符和空格就终止
例14:
#include<stdio.h>

#pragma warning(disable:4996)
int main()

{
char buf[20];
sscanf("agcd32DajfDdFF","%[a-z]",buf);
printf("%s\n",buf);//agcd

}
二、%[aBc]表示依照顺序检索并匹配字符串中的a、B、c,检索到其它字符和空格就终止

例:

#include<stdio.h>
#pragma warning(disable:4996)
int main()
{
char buf[20];
sscanf("adOa5Ffk780ad9fFafjdk","%[a0d5F]",buf);
printf("buf=%s\n",buf);//adoa5F
sscanf("ad0 a5Ffk780ad9fFafjdk",”%[a0d5F]",buf);
printf("buf=%s\n",buf);//ad0
return 0;
}
三、%[^aFc] 表示依照顺序检索并匹配字符串,检索到a、F、c就终止

例:

#include<stdio.h>
#pragma warning(disable:4996)
int main()

{
char buf[10];
sscanf("fdeo5609DtYfkadj",%[^Ooj]",buf);
printf("buf=%s\n",buf);//fde
sscanf("fd eo5609DtYfkadj","%[^Ooj]",buf);
printf("buf=%s\n",buf);//fd e
return 0;

}
四、%[^a-z] 表示依照顺序检索并匹配字符串,检索到a至z之间的字符就终止
例15:

#include<stdio.h>
#pragma warning(disable:4996)
int main()

{
char buf[20];
sscanf("13TwcyzRrjfkk",”%[^a-f]”,buf);
printf("buf=%s\n",buf);//13Tw
sscanf("13 TwcyzRrjfkk","%[a-f]",buf);
printf(“buf=%s\n”,buf);//13 Tw
return 0;
}

8.11const
1、修饰普通变量代表只读

例:
#include<stdio.h>
#pragma warning(disable:4996)
int main()

{
const int a = 100; //定义一个只读整型变量a的值为100,a的值无法修改
printf("a=%d\n",a);
a=200; //错误
printf("a=%d\n",a);
return 0;

}
2、const修饰指针变量
(1)const char *str//str指向的内存中的内容无法通过str进行修改,可以保护str指向的内存中的内容,但str的指向可以改变

例:

#include<stdio.h>

#include<string.h>
#pragma warning(disable:4996)
int main()

{
char buf[20] = "helloworld";
const char *str=buf;
strcpy(str,"hellokitty"); //错误
*str=’w’; //错误
printf("str=%s\n",str);//helloworld

str = hellokitty;

printf("str=%s\n",str);//hellokitty
return 0;

}
(2)char *const str//str是只读变量,无法改变指向,但str指向的内存中的内容有修改的可能性

例:

#include<stdio.h>
#pragma warning(disable:4996)
int main()

{
char buf[20] = "helloworld";
const char *str=buf;
printf("str=%s\n",str);//helloworld
*str=’w’;
printf("str=%s\n",str); //welloworld
return 0;

}
(3)const char *const str//str无法改变指向,且指向的内存中的内容也无法通过str进行修改

例:

#include<stdio.h>
#pragma warning(disable:4996)
int main()

{
char buf[20] = "helloworld";
const char *str=buf;
printf("str=%s\n",str);//helloworld
*str=’w’; //错误

str = hellokitty; //错误
return 0;

}
 

练习1:实现strlen函数功能,如unsigned int my_strlen(const char *s);

例:

#include<stdio.h>
#pragma warning(disable:4996)
unsigned int my_strlen(const char* s)

{
const char* p=s;
while(*p!=’\0’)

{
p++;

}

return p - s;

}
int main()

{
int num;
num = my_strlen(helloworld);
printf("num=%d\n",num);
return 0;
}
练习2:实现strcpy函数功能,如char *my_strcpy(char *dest,const char *src);

例:

#include<stdio.h>
#pragma warning(disable:4996)
char* my_strcpy(char* dest,const char* src)

{
char* pl = dest;
const char* p2 = src;
while (*p2!=’\0’)//"hello world"
{
*p1=*p2;
p1++;
p2++;

}
*p1=’\0’;
return dest;

}
int main()

{
char buf[100];
my_strcpy(buf,"hello world");
printf("buf=%s\n",buf);
return 0;
}
练习3:实现atoi函数功能,如int my_atoi(const char* str)

例:

#include<stdio.h>
#pragma warning(disable:4996)
int my_atoi(const char* str)

{
int tmp=0;
while(*str!=’\0’)

{
tmp = tmp * 10 + (*str-’0’);
str++;
}
return tmp;

}
int main()

{
int num;
num = my_atoi("1234");
printf("num=%d\n",num);
return 0;
}
练习4:使用sscanf函数读取字符串"[ti:简单爱]"中的":"与"]"之间的内容

例:

#include<stdio.h>
#pragma warning (disable:4996)
int main()
{
char buf[20];
sscanf("[ti:简单爱]",”%*[^:]%*c%[^]]”,buf);
printf("buf=%s\n",buf);
return 0;

}
练习5:使用sscanf函数读取字符串"[02:06.85]"中的02(代表分钟)和06(代表秒钟)赋值给整型变量min、sec

例:

#include<stdio.h>
#pragma warning(disable:4996)
int main()
{
int min,sec;
sscanf(“[02:06.85]”,"[%2d:%2d",&min,&sec);
printf(“min=%02d\n”,min);
printf("sec=%02d\n",sec);
return 0;

}
练习6:使用strchr函数查找字符,如统计”helloworldhelloworldhelloworld”字符串中’w’的个数及位置

例:
#include<stdio.h>
#include<string.h>
#pragma warning(disable:4996)
int main()
{
char* str=”helloworldhelloworldhelloworld”;
int num=0;
char* p;
char* q = str;
while((p=strchr(q,’w’)) !=NULL)

{
num++;
printf("第%d个’w’的位置是%d\n",num,p - str);
q=p+1;
}
printf("总共找到%d个’w’\n",num);
return 0;

}
9.1结构体概念
    程序开发时,某些情况需要将不同类型的数据组合成一个有机的整体,便于调用,如一个学生包含学号/姓名/性别/年龄/地址等信息
int num;
char name[20];
char sex;
int age;
char addr[30];
//单独定义以上变量比较繁琐,且数据不便管理,所以c语言发明结构体类型
结构体和数组都是构造类型:

(1)结构体是由若干个相同或不同类型的数据构成的集合,既不是基本类型的数据结构也不是指针类型的数据结构
(2) 数组是由若干个相同类型的数据构成的有序集合,用于处理大量相同类型的数据运算
9.2结构体类型定义
(1)先定义结构体类型,再定义结构体变量
struct 结构体类型名{
成员列表;
};
例1:
struct stu{
int num;
char name[20];
char sex;
};//定义结构体类型后,就可以用类型定义变量
struct stu lucy,bob,lilei;//定义三个struct stu类型的变量,每个变量都有三个成员,分别是num、name、sex
可以暂时认为结构体变量的大小是它所有成员之和
(2)确定结构体类型的同时,也定义结构体变量,之后仍可以定义结构体变量
struct 结构体类型名{
成员列表;
}结构体变量1,变量2;
struct 结构体类型名 变量3,变量4;
例2:
struct stu{
int num;
char name[20];
char sex;

}lucy,bob,lilei;
struct stu xiaohong,xiaoming;
(3)定义结构体类型时,没有结构体类型名,同时定义结构体变量,由于没有结构体类型名,所以之后不能再定义相关类型的数据
struct{
成员列表;
}变量1,变量2;
例3:
struct{
int num;
char name[20];
char sex;
}lucy,bob;
(4)重命名结构体的方法:通常将一个结构体类型重新命名,使用新的类型名代替原先的类型名
步骤1:先使用结构体类型定义变量
struct stu{
int num;
char name[20];
char sex;
}bob;
步骤2:使用新的类型名替代原先定义的变量名
struct stu{
int num;
char name[20];
char sex;
}STU;
步骤3:在结构体前面加typedef修饰
typedef struct stu{
int num;
char name[20];
char sex;
}STU;//STU相当于struct stu
注意:步骤3是编程过程中的代码,步骤1和步骤2是重命名的演示部分
9.3结构体变量的定义、初始化和使用
1、结构体变量的定义和初始化
1)定义结构体变量首先要有结构体类型,然后再定义变量
2)定义结构体变量时,可以同时将结构体变量初始化
3)结构体变量初始化时,各个变量按照顺序进行初始化
例4:
struct stu{
int num;
char name[20];
char sex;
};
struct stu boy;
struct stu lucy={
101,
"lucy",
‘m’
};
2、结构体变量的使用
1)结构体变量成员的引用方法:结构体变量.成员名
例5:
struct stu{
int num;
char name[20];
char sex;
};
struct stu bob;
bob.num=101;//bob是结构体变量,但bob.num是int类型的变量
bob.name;//bob.name是字符数组名,代表字符数组的地址,是常量
bob.name="bob";//错误
strcpy(bob.name,"bob");

例6:
#include <stdio.h>

#pragma warning(disable:4996)
struct stu{
int num;
char name[20];
int score;
char *addr;
};
int main(int argc,char *argv[])
{
struct stu bob;
printf("%d\n",sizeof(bob));
printf("%d\n",sizeof(bob.name));
printf("%d\n",sizeof(bob.addr));
return 0;

}
2)结构体成员多级引用
例7:
#include <stdio.h>

#pragma warning(disable:4996)
struct date{
int year;
int month;
int day;
};
struct stu{
int num:
char name[20];
char sex;
struct date birthday;
};
int main(int argc,char *argv[])
{
struct stu lilei={101,"lilei",’m’};
lilei.birthday.year=2000;
lilei.birthday.month=1;
lilei.birthday.day=8;
printf("%d %s %c\n",lilei.num, lilei.name,lilei.sex);
printf("%d %d %d\n"lilei.birthday.year,lilei.birthday.month,lilei.birthday.day);
return 0;

}
3、相同类型的结构体变量可以相互赋值
例8:
#include <stdio.h>

#pragma warning(disable:4996)
struct stuf{
int num;
char name[20];
char sex;
}:
in main(int argc,char *argv[])
{
struct stu bob={101,"bob",’m’};
struct stu lilei;
lilei=bob;
printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);
return 0;
}
9.4结构体数组
结构体数组是由若干个相同类型的结构体变量构成的集合
1、结构体数组的定义方法:
struct 结构体类型名 数组名[元素个数];
例9:
struct stu{
int num;
char name[20];
char sex;
};
struct stu edu[3];//定义一个struct stu类型的结构体数组edu,数组edu的3个元素分别是edu[0]、edu[1]、edu[2]
2、结构体数组元素的使用:数组名[下标].结构体变量,如
edu[0].num=101;
strcpy(edu[1].name,"lucy");
例10:
#include <stdio.h>

#pragma warning(disable:4996)
typedef struct student
{
int num;
char name[20];
float score;
}STU;
STU edu[3]={
{101,"Lucy",78},
{102,"Bob",59.5},
{103,"Tom",85}
};
int main()
{
int i;
float sum=0;
for(i=0;i<3;i++)
{
sum+=edu[i].score;
}
printf("平均成绩为%f\n",sum/3);
return 0;
}
9.5结构体指针
即结构体的地址
1、结构体指针变量的定义方法:
struct 结构体类型名* 结构体指针变量名;
struct stu{
int num;
char name[20];
};
struct stu* p;//定义一个struct stu*类型的指针变量p,用来保存结构体变量的地址编号
struct stu boy;
p=&boy;
访问结构体变量的成员方法:
例11:
boy.num=101;//通过结构体变量名.成员名
(*p).num=101;//*p相当于p指向的结构体变量boy
p->num=101;//通过指针->成员名
注意:通过结构体指针来引用指针指向的结构体中的成员,前提是结构体指针必须先指向一个结构体变量
结构体指针应用场景:
1)保存结构体变量的地址
例12:

#include <stdio.h>

#pragma warning(disable:4996)
typedef struct stu{
int num;
char name[20];
float score;
)STU;
int main()
{
STU *p,lucy;
p=&lucy;
p->num=101;
strcpy(p->name,"baby");
//p->name="baby";//错误, p->name相当于lucy.name,lucy.name是字符数组名,是常量

return 0;

}
2)传结构体变量的地址
例13:
#include<stdio.h>
#include<string.h>

#pragma warning(disable:4996)
typedef struct stu{
int num;
char name[20];
float score;
}STU;
void fun(STU *p)
{
p->num=101;
(*p).score=87.6;
strcpy(p->name,"lucy");
int main()

{
STU girl;
fun(&girl);
printf("%d %s %f\n",girl.num,girl.name,girl.score);
return 0;
}
3)传结构体数组的地址
结构体数组的地址是第0个结构体变量的地址
例14:
#include<stdio.h>
#include<string.h>

#pragma warning(disable:4996)
typedef struct stu{
int num;
char name[20];
float score;
}STU;
void fun(STU *p)
{
p[1].num=101;
(*(p+1)).score-=88.6;
}
int main()
{
STU edu[3];
fun(edu);
printf("%d %f\n",edu[1].num,edu[1].score);
return 0;
}

注意:
1)结构体变量的地址编号和结构体内第一个成员的地址编号相同,但指针的类型不同
例15:
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
int num;
char name[20];
int score;
};
int main(int argc,char *argv[])
{
struct stu bob;
printf("%p\n",&bob); //struct stu*
printf("%p\n",&(bob.num)); //int*
return 0;
}
2)结构体数组的地址就是结构体数组中第0个元素的地址
例16:
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
int num;
char name[20];
int score;
};
int main(int argc,char *argv[])
{
struct stu edu[3];
printf("%p\n",edu);//struct stu*
printf("%p\n",&(edu[0]));//struct stu*
printf("%p\n",&(edu[O].num));//int*
return 0;
}
9.6结构体内存分配
1、结构体内存分配
结构体变量的大小是它所有成员的大小之和
例17:
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
int num;
int age;
}lucy;
int main()
{
printf("%d\n",sizeof(lucy));//结果为8
return 0;
}
注意:给结构体变量分配内存是有规则的
例18:
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
char sex;
int age;
}lucy;
int main()
{
printf("%d\n",sizeof(lucy));
return 0;

}
规则1:以多少个字节为单位开辟内存
给结构体变量分配内存时,根据结构体变量中占字节数多的基本类型的成员,以它的大小为单位开辟内存,但在gcc中出现double类型的除外
1)成员中只有char类型数据,以1字节大小为单位开辟内存
2)成员中出现short int类型数据,而没有占字节数更多的基本类型数据,以2字节大小为单位开辟内存
3)出现int、float类型数据,而没有占字节数更多的基本类型数据,以4字节大小为单位开辟内存
4)出现double类型的数据
情况1:
在VC6.0和Visual Studio中,以8字节为单位开辟内存
情况2:
在Linux环境gcc中,以4字节为单位开辟内存
无论哪种环境,double类型变量占8字节
注意:
1)如果在结构体中出现数组,可以将数组看成多个变量的集合
2)如果在结构体中出现指针,而没有占字节数更多的基本类型数据时,以4字节大小为单位开辟内存
3)在内存中存储结构体成员时,按照定义的结构体成员的顺序进行存储
例19:
struct stu{
char sex;
int age;
}lucy;//lucy占用的字节数是4的倍数

规则2:字节对齐
1)char:1字节对齐,即存放char型变量的内存单元的编号是1的倍数即可
2)short int:2字节对齐,即存放short int型变量的起始内存单元的编号是2的倍数即可
3)int:4字节对齐,即存放int型变量的起始内存单元的编号是4的倍数即可
4)long int:4对齐,即存放long int型变量的起始内存单元的编号是4的倍数即可
5)f1oat:4字节对齐,即存放f1oat型的变量的起始内存单元的编号是4的倍数即可
6)double:

一、 在VC6.0和Visual Studio环境下,8字节对齐,即存放double型变量的起始内存单元的编号是8的倍数
二、在gcc环境下:4字节对齐,即存放double型变量的起始内存单元编号是4的倍数
例20:temp的大小为8个字节
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
char a;
short int b;
int c;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p\n",&(temp.c));
return 0;
}
结果分析:a和b的地址差2个字节,b和c的地址差2个字节
例21:temp的大小为12个字节
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
char a;
int c;
short int b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p n",&(temp.c));
return 0;
}
结果分析:a和c的地址差4个字节,c和b的地址差4个字节

原因:在内存中存储结构体成员时,按照定义的结构体成员的顺序进行存储
例22:temp占16个字节
struct stu{
char buf[10];
int a;
}temp;
例23:
在vc中占16个字节,a和b的地址差8个字节
在gcc中占12个字节,a和b的地址差4个字节
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
char a;
double b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
字节对齐的原因:使用内存空间换取运算时间,提高cpu读取数据的效率
struct stu{
char a;
int b;
};

7

b3

7

6

b2

6

5

b1

5

4

b0

4

b3

3

3

b2

2

2

b1

1

1

b0

0

a

0

a

存储方式1        存储方式2
指定对齐原则:使用#pragma pack改变默认对齐原则
格式:使用#pragma pack(value)时,指定对齐值value
注意:1)value的值只取1、2、4、8等
2)指定对齐值与数据类型对齐值相比取较小值
1、以多少个字节为单位开辟内存
结构体成员中占字节数最多的数据类型长度与value比较,取较小值为单位开辟内存
例24:以2个字节为单位开辟内存
#include <stdio.h>

#pragma warning(disable:4996)
#pragma pack(2)
struct stu{
char a;
int b;
}temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}//temp的大小为6个字节,a和b的地址差2个字节
例25:以4个字节为单位开辟内存
#include <stdio.h>

#pragma warning(disable:4996)
#pragma pack(8)
struct stu{

char a;
int b;
)temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}//temp的大小为8个字节,a和b的地址差4个字节
2、字节对齐
结构体中成员的对齐方法:各个默认的对齐字节数和value相比,取较小值
例26:
#include <stdio.h>

#pragma warning(disable:4996)
#pragma pack(2)
struct stu{
char a;
int b;
)temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}//成员a是1字节对齐,成员b是2字节对齐,a和b的地址差2个字节
例27:
#include <stdio.h>

#pragma warning(disable:4996)
#pragma pack(8)
struct stu{
char a;
int b;
)temp;
int main()
{
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}//成员a和成员b都按原先的对齐方式存储
注意:指定对齐值设为l时,则short int、int、float等均为l字节对齐,指定对齐值设为2时,则char仍为1字节对齐,short int、int、float等都为2字节对齐
9.7位段
一、位段
结构体中以位(bit)为单位的成员,称为位段(位域)
struct stu{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
}data;//以4字节为单位开辟内存空间

a

b

c

d

i

2

6

4

4

16

32

注意:位段成员不能取地址
例28:
#include<stdio.h>

#pragma warning(disable:4996)
struct stu{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
}data;
int main()
{
printf("%d\n",sizeof(data));
printf("%p\n",&data);
printf("%p\n",&(data.i));
return 0;
}
注意:1、位段成员的引用:data.a=2;
赋值时不要超出位段定义的范围,如位段成员a定义为2位,十进制最大值是3,即二进制是11

如果data.a=5,则二进制是101,赋值时会取101的低两位,即01,十进制是1
2、位段成员的类型必须是整型或字符型
3、一个位段必须存放在一个存储单元中,不能跨越两个单元,所以一个单元空间容纳不下一个位段时,则不使用该单元空间,而从下一个单元起存放该位段

位段的存储单元:
1)char型位段的存储单元是1个字节
2)short int型位段的存储单元是2个字节
3)int型位段的存储单元是4字节
4)long int型位段的存储单元是4字节
例29
#include <stdio.h>

#pragma warning(disable:4996)
struct stu{
char a:7;
char b:7;
char c:2;
}temp; //temp的大小为3个字节,成员b不能跨越单元空间进行储存
int main()
{
printf("%d\n",sizeof(temp));
return 0;

}
注意:不能取temp.b的地址,因为temp.b的大小可能不够1个字节
4、位段的长度不能大于存储单元的长度
1)char型位段不能大于8位
2)short int型位段不能大于16位
3)int型位段不能大于32位
4)long int型位段不能大于32位
例30:
#include <stdio.h>

#pragma warning(disable:4996)
struct stu{
char a:9;//错误
char b:7;
char c:2;
)temp;
int main()
{
printf("%d\n",sizeof(temp));
return O;
}
5、一个位段要从另一个存储单元开始,定义如下:
unsigned char a:1;
unsigned char b:2;
unsigned char:0;
unsigned char c:3;(另一个储存单元)
//使用大小为0的位段,其作用是使下一个位段从下一个存储单元开始存放,从而将a、b存放在同一个储存单元中,c另存在下一个储存单元中
例31
#include <stdio.h>

#pragma warning(disable:4996)
struct stu{
unsigned char a:1;
unsigned char b:2;
unsigned char :0;
unsigned char c:3;

}
int main()
{
struct stu temp;
printf("%d\n"sizeof(temp));
return 0;
}
6、可以定义无意义位段,如:
unsigned a:1;
unsigned :2;
unsigned b:3;

//a、b存放在同一个存储单元中,但两者之间相隔2位
9.8共用体
共用体和结构体类似,也是一种构造类型的数据结构

定义共用体时,要求先定义出数据类型,然后定义变量
定义共用体类型的方法和结构体类似,只需将struct改成union即可
进行某些算法时,需要将几种不同类型的变量存放在同一个内存单元中,几个变量所使用的内存空间相互重叠
这种几个不同类型的变量共同占用一段内存空间的结构,在c语言中被称为“共用体”类型结构,共用体的大小是其占内存长度最大的成员的大小
例34:
#include <stdio.h>
#pragma warning(disable:4996)
typedef union data{
short int i;
char ch;
float f;
}DATA;
int main()
{
DATA temp; //共用体temp占4个字节,即i、ch、f共用4个字节大小的内存空间
printf("%d\n",sizeof(temp));
printf("%p\n",&temp);
printf("%p\n",&(temp.i));
printf("%p\n",&(temp.ch));
printf("%p\n"&(temp.f));
return O;

}//结果:共用体temp占4个字节,后面的几个地址相同,证明共用体的各个成员占用同一段内存空间
共用体的特点:
1) 几种不同类型的成员存放在同一段内存空间中,但每次只有一种类型起作用
2)共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后,原有成员的值会被覆盖
3)共用体变量的地址和它的各成员的地址相同
4)共用体变量的初始化:
union data a={123};//共用体初始化时只能给第一个成员赋初值,不能给所有成员都赋初值
例35:
#include <stdio.h>

#pragma warning(disable:4996)
typedef union data{
unsigned char a;
unsigned int b;
}DATA;
int main()
DATA temp;
temp.b=0xffffffff;
printf("temp.b = %x\n",temp.b);//ffffffff

printf("temp.a = %x\n",temp.a);//ff
temp.a=0x55;
printf("temp.a = %x\n",temp.a);55
printf("temp.b = %x\n",temp.b);ffffff55
return 0;
}

9.9枚举
概念:在枚举值列表中列出所有待用值,即枚举元素,变量的值只限于列举出来的值的范围内,枚举元素是常量,默认从0开始编号
枚举类型是构造类型,类型定义与结构体类型的定义类似
使用枚举时,要求先定义枚举类型,再定义枚举变量
1、枚举类型的定义方法:
enum 枚举类型名{
枚举值列表;
};
2、枚举变量的定义方法
enum 枚举类型名 枚举变量名;
例37:定义枚举类型week
enum week{
mon,tue,wed,thu,fri,sat,sun
};
enum week workday,weekday;//定义枚举变量
workday与weekday只能取mon~sun中的一个
workday=mon;//正确
weekday=tue;//正确
workday=abc;//错误,枚举值中没有abc
1)枚举值是常量,不能进行赋值
如sun=5;mon=2;sun=mon;//都是错误的
2)枚举元素的值是由编译系统定义的一个表示序号的数值,默认从0开始按照顺序定义为0,1,2…,如在枚举类型week中,mon值为0,tue值为1,…,sun值为6
3)可以改变枚举值的默认值,如
enum week//枚举类型
{
mon=3,tue,wed,thu,fri=4,sat,sun
};
//mon=3,则tue=4,以此类推;但是fri=4,则sat=5,以此类推
注意:定义枚举类型时可以用等号给枚举元素赋值,代表枚举元素从几开始编号

第10章文件
10.1文件的概念
文件是用来存放程序、文档、音频、视频、图片等数据的,可以认为文件是存放在磁盘上的一些数据的集合
使用windows时可以通过写字板或记事本打开文本文件对文件进行编辑保存
10.1.1文件的分类:
磁盘文件(通常使用的文件): 一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,文件使用时才会调入内存中
设备文件:在操作系统中将每一个与主机相连的输入、输出设备看作是一个文件,将它们的输入、输出等同于对磁盘文件的读取与写入,如键盘(标准输入文件)、     屏幕(标准输出文件)等
其它设备:打印机、触摸屏、摄像头、音响等
在Linux操作系统中,每一个外部设备都在/dev目录下对应着一个设备文件,如果想要在程序中操作设备,就必须对与其对应的/dev目录下的设备文件进行操作
标准io库函数对磁盘文件的读取特点

内存

输出

磁盘

程序

数据区

文件缓冲区(系统\程序)

文件

输入

文件缓冲区(系统程序)

文件缓冲区是库函数申请的一段内存空间,由库函数对其进行操作,进行编程过程中只需要知道对文件操作时的一些缓冲特点即可
在Visual Studio中对普通文件的读取与写入是全缓冲的
标准io库函数往普通文件读写数据是全缓冲的
刷新缓冲区的情况:
1)缓冲区存满,自动刷新缓冲区
2)调用函数刷新缓冲区fflush(文件指针)
3)程序结束时自动刷新缓冲区
10.1.2磁盘文件的分类
一个文件通常是磁盘上一段命名的存储区
计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的,即以字节为单位进行顺序存储
从用户或者操作系统使用的角度(逻辑上)把文件分为文本文件和二进制文件
1)文本文件:基于字符编码,常见编码有ASCII、UNICODE等,一般可以使用文本编辑器直接打开,如数字5678以ASCⅡ码形式存储为:
00110101 00110110 00110111 00111000
2)二进制码文件:基于值编码,以位来表示一个意思,根据具体应用来指定某个值的意思,一般需要判断或使用特定软件进行分析数据格式,如数字5678以二进制码形式存储为:00010110 00101110
音频文件(mp3):二进制文件
图片文件(bmp):一个像素点由两个字节来描述,如*****#####&&&&&(565)
*代表红色的值R
#代表绿色的值G
&代表蓝色的值B
文本文件和二进制文件的对比:
译码:
文本文件编码基于字符定长,译码较容易
二进制文件编码是变长的,译码较难(不同二进制文件格式有不同的译码方式,一般需要特定软件进行译码)
空间利用率:
二进制文件用一位(bit)来表示一个意思(位操作),空间利用率高
文本文件任何一个意思至少是一个字符
可读性:
文本文件使用通用的记事本工具几乎可以浏览所有文本文件
二进制文件需要一个具体的文件解码器,如BMP文件必须使用读图软件
总结:
1)文件在硬盘上存储时,物理上都是用二进制来进行存储的
2)标准io库函数对文件操作时,不管文件的编码格式(字符编码或二进制),而是按字节对文件进行读写,所以文件又称流式文件,即把文件看成字节流
10.2文件指针
文件指针在程序中用来代表一个文件,打开文件时获得文件指针
想要对文件进行读、写和关闭等操作时,对文件指针进行操作即可,即将文件指针传给读、写和关闭等函数
定义文件指针的一般形式为:

头文件:#include <stdio.h>
FILE* 文件指针变量标识符;
FILE是系统使用typedef定义的有关文件信息的一种结构体类型,结构体中含有文件名、文件状态和文件当前位置等信息
一般情况下,我们操作文件前必须定义一个文件指针变量代表将要操作的文件,实际编程中使用库函数操作文件时,无需知道FILE结构体的内容,只需将文件指针变量传给io库函数,库函数再通过FILE结构体里的信息对文件进行操作
FILE在<stdio.h>文件中的文件类型声明:
typedef struct
{
short level;           //缓冲区“满”或“空”的程度
unsigned flags;        //文件状态标志
char fd;               //文件描述符
unsigned charhold;     //如无缓冲区不读取字符
short bsize;           //缓冲区的大小
unsigned char *buffer;//数据缓冲区的位置
unsigned ar*curp;      //指针的当前指向
unsigned istemp;       //临时文件指示器
shorttoken;             //用于有效性检查

}FILE;
在缓冲文件系统中,每个被使用的文件都要在内存中开辟一片FILE类型的区域,存放与操作文件相关的信息
              testl.txt           test2.txt           test3.txt

FILE *fp1             FILE *fp2            FILE *fp3

     FILE                 FILE                 FILE

对文件操作的步骤:
1)对文件进行读写等操作前要求打开文件获到文件指针
2)可以通过文件指针变量对文件进行读写等操作
3)完成读写等操作后,要求关闭文件,关闭后就不能再使用此文件指针操作文件
补充:c语言中有三个特殊的文件指针无需定义,可以直接使用
stdin:标准输入,默认为当前终端(键盘)
scanf和getchar等函数默认从此终端获得数据
stdout:标准输出,默认为当前终端(屏幕)
printf和puts等函数默认输出信息到此终端
stderr:标准错误输出设备文件,默认为当前终端(屏幕)
程序出错时使用perror函数打印信息在此终端
10.3打开文件fopen

头文件:#include <stdio.h>
函数声明:FILE *fopen(const char *path,const char *mode);
函数功能:打开一个已经存在的文件并返回文件指针,或者创建一个文件并打开此文件,然后返回文件指针
函数的参数:
参数1:打开的文件的路径
1)绝对路径:从根目录开始的路径名称
"D:\\demo\\test\\aaa.txt"
2)相对路径
.\\test\\aaa.txt
参数2:文件打开的方式
读写权限:r、w、a、+
>r:以只读方式打开文件
1)文件不存在,则返回NULL
2)文件存在且打开文件成功,则返回文件指针,然后进行后续的读取操作
例1:
FILE *fp;
fp=fopen(“test.txt”,”r");
>w:以只写方式打开文件
1)文件不存在,则以指定文件名创建此文件并打开文件
2)文件存在,则清空文件内容并打开文件,然后进行写入操作
3)如果文件无法打开,如只读文件,则返回NULL
FILE *fp;
fp=fopen("test.txt","");
>a:以追加方式打开文件
1)文件不存在,则以指定文件名创建此文件
2)文件存在,则从文件的结尾处进行写入操作
说明:
如果不使用a,则打开文件时读写位置在文件的开始
如果使用a,则打开已经存在的文件时读写位置在文件的末尾
>+:同时以读写打开指定文件

模式

功能

r或rb

以只读方式打开一个文件(不创建文件)

w或wb

以写入方式打开文件(使文件大小截断为0字节,创建一个文件)

a或ab

以追加方式打开文件,即在末尾添加内容,文件不存在时,创建文件用于写入

r+或rb+

以读写的方式打开文件(不创建新文件)

w+或wb+

以读写的方式打开文件(使文件大小截断为0字节,创建一个文件)

a+或ab+

以追加方式打开文件,并在末尾更改文件,若文件不存在,则创建文件

补充:没有b是以文本文件进行打开,有b是以二进制文件进行打开

返回值:
成功,则返回打开的文件对应的文件指针
失败,则返回NULL
调用fopen函数时,要判断打开是否成功
10.4关闭文件fclose
头文件:#include <stdio.h>
函数声明:int fclose(FILE *fp);
函数功能:关闭fp所代表的文件
注意:一个文件只能关闭一次,关闭后就不能再使用文件指针操作文件
返回值:
成功,则返回0
失败,则返回非0
例6:
#include <stdio.h>

#pragma warning(disable:4996)
int main()
{
FILE *fp;
int ret;
fp=fopen(".\\test.txt","r+");
if(fp==NULL)
{

printf("打开文件失败\n");
perror("fopen");//给出打开文件失败的原因
return 0;
}
printf("打开文件成功\n");
ret=fclose(fp);
if(ret==0)

{
printf("关闭文件成功\n");

}
else

{
printf("关闭文件失败\n");

}
return 0;
}
10.5一次读写一个字符
头文件:#include <stdio.h>

函数声明:int fgetc(FILE *stream);
函数功能:fgetc从stream所代表的文件中读取一个字节,并将字节值返回
返回值:
以t的方式:读到文件结尾,则返回EOF
以b的方式:读到文件结尾,需要使用feof(文件指针)判断结尾
feof是c语言标准库函数,其定义在<stdio.h>中,其功能是检测文件的结束符,如果文件结束,则返回非0,否则返回0

头文件:#include <stdio.h>
函数声明:int fputc(int c,FILE *stream)
函数功能:将c的值写入stream所代表的文件中
返回值:
成功,则返回写入的字节值
失败,则返回EOF
EOF是在<stdio.h>中定义的符号常量,值为-1
注意: 对文件进行读写操作时,读写位置会往文件的末尾方向偏移,读写多少个字节,则读写位置就往文件的末尾方向偏移多少个字节
例7:
#include <stdio.h>

#pragma warning(disable:4996)
int main()

{
FILE *fp;
char ch;
fp=fopen("test.txt","r+");
if(fp==NULL)
{
perror(“fopen”);
return 0;
}
while((ch=fgetc(fp))!=EOF)
{
fputc(ch,stdout);
}
fclose(fp);
return O;
}
10.6一次读写一个字符串
头文件:#include <stdio.h>

函数声明:char *fgets(char *s,int size,FILE *stream);
函数功能:从stream所代表的文件中读取字符,读取时读到换行符或者读到文件末尾就停止,或者是读取size-1个字节就停止,在读取的内容后面会加一个’\0’作为字符串的结尾
返回值:
成功,则返回目标数组的首地址,即返回传给形参s的数组的首地址
失败,则返回NULL

头文件:#include <stdio.h>
函数声明:int fputs(const char *s,FILE *stream);
函数功能:将s指向的字符串写入stream所代表的文件中
返回值:
成功,则返回写入的字节数
失败,则返回-1
例9:
#include <stdio.h>

#pragma warning(disable:4996)
int main()

{
FILE *fp_read,*fp_write;
char str[100];
if((fp_read=fopen("src.txt","r"))==NULL)
{
perror(“fopen”);
return 0;
}

if((fp_write=fopen("dest.txt","w"))==NULL)
{
perror(“fopen”);
return 0;
}
fgets(str,100,fp_read);
printf("%s\n",str);
fputs(str,fp_write);
fclose(fp_read);
fclose(fp_write);
return 0;
}
10.7读文件fread
头文件:#include <stdio.h>

函数声明:
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数功能:从stream所代表的文件中读取数据,读取一组是size个字节,共nmemb组,将数据存放在ptr指向的内存中
返回值:实际读取的组数
例1:
int num;
num=fread(str,l00,3,fp);
从fp所代表的文件中读取内容存放到str指向的内存中,读取300个字节

返回值num:
读取300个字节,则返回值num为3
读取200<=字节数<=300,则返回值num为2
读取100<=字节数<=200,则返回值num为1
读取字节数<100,则返回值num为0
10.8写文件fwrite

头文件:#include <stdio.h>
函数声明:
size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);

size_t实际是无符号整型,它是在头文件中用typedef进行重新定义的
函数功能:将ptr指向的内存里的数据写入stream所代表的文件中,一组是size个字节,共nmemb组
返回值:实际写入的组数
例10:
#include <stdio.h>

#pragma warning(disable:4996)
struct stu
{

char name[10];
int num;
int age;
}boya[2],boyb[2];
int main()
{
FILE *fp;
int i;
if((fp=fopen("test.txt","wb+"))==NULL)
{
perror(“fopen”);
return 0;

}
for(i=0;i<2;i++)

{

printf(“输入第%d个结构体的name num age\n”,i);
scanf("%s %d %d",boya[i].name,&(boya[i].num),&(boya[i].age));
fwrite(boya,sizeof(struct stu),2,fp);//将学生信息写入文件中
rewind(fp);//写操作位置在文件末尾,需要复位
fread(boyb,sizeof(struct stu),2,fp);//将文件中的数据读入到内存中
for(i=0;i<2;i++)

{
printf("%s %d %d\n",boyb[i].name,boyb[i].num,boyb[i].age);

}
fclose(fp);
return 0;

}
10.9随机读写
文件的读写方式都是按照顺序读写,实际应用中往往只要求读写文件中某一指定的部分,如读取文件第200~300个字节
随机读写:移动文件内部的位置指针到需要读写的位置,再进行读写
实现随机读写的关键是按照要求移动位置指针,即文件定位
完成文件定位的函数:rewind函数和fseek函数
1)头文件#include <stdio.h>

函数声明:void rewind(文件指针);
函数功能:将文件内部的读写位置指针移动到文件开头
调用形式:rewind(文件指针);
例12:
fwrite(pa,sizeof(struct stu),2,fp);
rewind(fp);
fread(pb,sizeof(struct stu),2,fp);
2) 头文件#include <stdio.h>

函数声明:long ftell((文件指针);
函数功能: 测量文件读写位置距文件开头有多少个字节
返回值:返回当前读写位置,即距离文件起始的字节数,出错时返回-1

例如:
long int length;
length=ftell(fp);
3) 头文件#include <stdio.h>
函数声明:int fseek(FILE *stream,long offset,int whence);
//int fseek(文件类型指针,位移量,起始点);
函数功能:移动文件流的读写位置
参数:
1)whence起始位置:
文件开头       SEEK_SET      0
文件当前位置   SEEK_CUR      1
文件末尾       SEEK_END      2
2)位移量:
以起始点为基点,向前、后移动的字节数,正数往文件末尾方向偏移,负数往文件开头方向偏移

注意:fseek函数一般用于二进制文件,即打开文件的方式需要有b
例13:
fseek(fp,50,SEEK_SET);
fseek(fp,-50,SEEK_END);
fseek(fp,0,SEEK_END);
fseek(fp,20,SEEK_CUR);
练习:将一个未知大小的文本文件全部读入内存,并显示在屏幕上
参考:fseek、ftell、rewind、fread、malloc
l)使用fopen函数打开文件,注意用b的方式打开
2)使用fseek函数定位文件的读写位置到文件末尾
3)使用ftell函数测量文件的字节数len
4)使用rewind函数复位读写位置到文件的开始
5)根据第3步得到的字节数,使用malloc函数申请内存,注意多申请一个字节存放’\0’
6)使用fread函数从文件中读取内容,存到申请的空间里
7)最后一个字节变成’\0’
8)使用printf函数打印读取的内容到屏幕上
9)使用fclose函数关闭文件
l0)使用free函数释放内存
#include<stdio.h>

#include<stdlib.h>
#pragma warning (disable:4996)
int main()

{
FILE* fp1;

long int len;

char *str;
fpl=fopen("简单爱.lrc","rb");
if(fp1 == NULL)
{
perror("fopen");
return 0;
}
fseek(fp1,0,SEEK_END);
len = ftell(fpl);
rewind(fp1);
str = malloc(len + 1);
if (str == NULL)
{
perror(“malloc”);
return 0;

}
fread(str,len,1,fp1);
str[len] = ’\0’;
printf("%s\n",str);
fclose(fpl);
free(str);
return 0;

}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值