⛄️上一篇⛄️C语言详细知识点(上)
文章目录
- 五、数组
- 1、一维数组的定义及使用
- 2、二维数组的定义及使用
- 3、字符数组的定义及使用
- 六、函数
- 1、函数的定义
- 2、函数的调用
- 3、函数的声明
- 4、函数的嵌套调用
- 5、函数的递归调用
- 七、指针
- 1、什么是指针
- 2、指针变量
- 3、通过指针引用数组
- 4、用数组名做函数参数
- 5、通过指针引用字符串
- 八、结构体
- 1、结构体类型的定义
- 2、使用结构体数组
- 3、结构体指针
五、数组
(1)数组是一组有序
数据的集合
(2)用一个数组名
和下标
来唯一地确定数组中的元素
(3)数组中的每一个元素都属于同一个数据类型
1、一维数组的定义及使用
(1)数组声明
语法:
类型 数组名[常量表达式];
注意:
1)类型是任意合法的类型
2)数组名遵循标识符命名规则
3)定义数组时,需要制定数组中元素的个数,int a[10],不存在a[10]
4)常量表达式可以包括常量和符号常量
,不能包含变量
(2)数组引用
语法:
数组名[下标];
注意:
1)只能引用数组元素而不能一次整体调用整个数组全部元素的值
2)定义数组时用到的数组名[常量表达式]
和引用数组元素时用的数组名[下标]
形式相同,但含义不同
如:int a[10]; //前面有int,这是定义数组,指定数组中包含10个元素
t=a[6]; //这里表示引用数组中序号为6的元素
例1、对10个数组元素依次赋值为0,1,2,3,4,5,6,7,8,9
,要求逆序输出
#include <stdio.h>
void main()
{
int a[10],i;
for(i=0;i<10;i++){
a[i]=i;
}
for(i=9;i>=0;i--){
printf("%d\t",a[i]);
}
}
(3)数组初始化
1)在定义数组时对全部数组元素赋予初值
如:int a[10]={0,1,2,3,4,5,6,7,8,9};
2)可以只给数组中的一部分元素赋初值
如:int a[10]={0,1,2,3,4};
3)如果想使一个数组中全部元素值为0
如 :int a[10]={0,0,0,0,0,0,0,0,0,0}; 或 int a[10]={0};
4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度
如:int a[5]={1,2,3,4,5}; 可以写成 int a[]={1,2,3,4,5};
注意:如果在定义数值型数组时,指定了数组的长度并对之初始化,凡未被初始化列表指定初始化的数组元素,系统会把它们初始化为0
(如果是字符型
数组,则初始化为'\0'
,如果是指针型数组
,则初始化为NULL
,及空指针)
(4)数组举例
例1、用数组解决斐波那契数列问题,求前20个斐波那契数列
#include <stdio.h>
void main()
{
int a[20],i,t=0;
a[0]=1;
a[1]=1;
for(i=2;i<20;i++){
a[i]=a[i-1]+a[i-2];
}
for(i=0;i<20;i++){
if(i%4==0)printf("\n");
printf("%d\t",a[i]);
}
}
例2、有10个地区面积,要求对它们按由小到大的顺序排列(冒泡排序)
#include <stdio.h>
void main()
{
int a[10], i, j, t;
for (i=0; i<10;i++){
scanf("%d", &a[i]);
}
for(i=0;i<10-1;i++)//外层循环n-1次
{
for(j=0;j<10-i-1;j++)//内层循环比较次数-1
{
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for (i = 0; i < 10; i++){
printf("%d\n", a[i]);
}
}
2、二维数组的定义及使用
一维数组的数组
放批量同类型数据,数组元素在内存中连续存放(按行存放,第一行存放完再存放第二行…),数组名代表数组首地址,是一个地址常量
(1)数组声明
语法:
类型 数组名[常量表达式] [常量表达式];
注意:
1)类型是任意合法的类型
2)[]里一定是常量,代表数组元素的个数
定义二维数组 float[3][4],C语言对二维数组采用这样的定义方式,使得二维数组可被看作一种特殊的一维数组:它的元素又是一个一维数组。把a看作一个一维数组,它有3个元素:a[0],a[1],a[2]
每个元素又是一个包含4个元素的一维数组
a[0]-----a[0][1] a[0][2] a[0][3] a[0][4]
a[1]-----a[1][1] a[1][2] a[1][3] a[1][4]
a[2]-----a[2][1] a[2][2] a[2][3] a[2][4]
可以把a[0],a[1],a[2]看作3个一维数组的名字,上面定义的二维数组可以理解为定义了3个一维数组相当于
float a[0][4] ,a[1][4],a[2][4],a[3][4]
(2)数组元素的引用
语法
数组名[行标][列标]
通常用双层循环遍历二维数组,外层控制行,内层控制列
(3)数组初始化(定义的同时赋值)
1)分行给二维数组赋初值
如:int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}
2)可以将所有数据写在一个花括号内,按数组元素在内存的排列顺序对各元素赋初值
如:int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
3)可以对部分元素赋初值
如:int a[3][4]={{1},{2},{3}};
4)如果全部赋值,则数组第一维可以不写,但第二维必须写
如:int a[2][3]={1,2,3,4,5,6} 等价于 int a[][3]={1,2,3,4,5,6}
例3、将一个二维数组的行和列互换,存到另一个二维数组中
#include <stdio.h>
void main()
{
int a[2][3]={{1,2,3},{4,5,6}};
int b[3][2],i,j;
for(i=0;i<2;i++){
for(j=0;j<3;j++){
b[j][i]=a[i][j];
}
}
}
例4、有一个3×4的矩阵,求出其中最大值和行号列号
#include <stdio.h>
void main()
{
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int i,j,max=1,r,l;
for(i=0;i<3;i++){
for(j=0;j<4;j++){
if(a[i][j]>max){
max=a[i][j];
r=i;
l=j;
}
} ;
}
printf("最大值%d行号%d列号%d",max,r,j);
}
3、字符数组的定义及使用
(1)数组声明
语法:
char 数组名[常量表达式];
(2)初始化
初始化列表,把各个字符以此赋值给数组中各元素,可以全部赋值,也可以部分赋值(其余元素都是默认值)全赋值数组大小可以省略
如:char a[6]={‘C’,‘h’,‘i’,‘n’,‘a’};
注意:
1️⃣如果在定义字符数组时不进行初始化,则数组中各个元素的值时不可预料的。
2️⃣如果花括号中提供的初值个数(即字符个数)大于数组长度,则出现语法错误
3️⃣如果初值个数小于数组长度
,则只将这些字符给数组中前面那些元素,其余的元素自动定义为空字符(即'\0')
(3)数组元素引用
数组名[下标]
(4)字符串结束标志
1)c系统在用字符数组存储字符串常量时会自动加一个’\0’作为结束字符
如:char a[6]={“China”}; 共有5个字符
,在数组中占6个字节
,最后一个’\0’是系统自动加上的
2)第二种字符串初始化方法
如:char a[]={“China”} 可以写成char a[]=“China”;
3)字符数组并不要求最后一个字符为’\0’,甚至可以不包含’\0’
如:char a[6]={‘C’,‘h’,‘i’,‘n’,‘a’};
(5)字符数组的输入输出
1)逐个字符输入输出,格式符%c
2)整个字符串输入输出,格式符%s
3)输出的字符不包括结束符’\0’
4)如果一个字符数组包含一个以上的’\0’,则遇到第一个’\0’时输出结束
5)系统把空格符作为输入的字符串之间的分隔符,因此只将空格前的字符"How"送到 str 中,吧"How"作为一个字符串处理,故在其后加’\0’
如:char str[13]; scanf("%s",str); 输入 How are you 输出 How
6)scanf函数中输入项如果是字符数组名,不要再加地址符&,八进制输出数组首地址 如:printf("%o",str);
(6)字符串处理函数
1️⃣puts输出字符串函数
语法:puts(字符数组)
作用:将一个字符串(以’\0’结束的字符序列)输出到终端,用puts函数输出的字符串可以包含转义字符
如: char str[]={"China\nBeijing"}; puts(str);
在用puts输出时将字符串结束标志'\0'转换成'\n'
,即输出玩字符串后换行
2️⃣gets输入字符串函数
语法:gets(字符数组)
作用:从终端输入一个字符串到字符串数组,并且得到一个函数值
如:char a[]={"Computer"};gets(a);
3️⃣strcat字符串连接函数
语法:stract(字符串数组1,字符串数组2)
作用:把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值------字符数组1的地址
如:
#include <stdio.h>
void main()
{
char str1[20]={"China"},str2[]={"Beijing"};
strcat(str1,str2);
printf("%s",str1);
}
ChinaBeijing
注意:①字符数组1必须足够大,以便容纳连接后的新字符串
②连接时将字符串1后面的’\0’取消,只在新字符串最后保留’\0’
4️⃣strcpy和strncpy字符串复制函数
语法:strcpy(字符数组1,字符数组2)
作用:将字符串2复制到字符串1中去
如:
#include <stdio.h>
void main()
{
char str1[]={"Bejing"},str2[]={"China"};
strcpy(str1,str2);
printf("%s",str1);
}
China
注意:①字符数组1必须定义足够大,以便容纳被定义的字符数组2,字符数组1的长度不应小于字符数组2的长度
②字符串2可以是数组名也可以是一个字符串常量
如:strcpy(str1,“China”);
③可以用strncpy将字符串2中前面n个字符复制到字符数组1中去,但复制的字符个数n不应多于原有的字符(不包括’\0’);
如:strncpy(str1,str2,2);
5️⃣strcmp字符串比较函数
语法:strcmp(字符串1,字符串2)
作用:比较字符串1和字符串2
如:strcmp(“China”,“Beijing”);
说明:字符串比较规则是:将两个字符串自左至右逐个字符相比(按照ASCII码值大小比较,小写字母比大写字母大
),直到出现不同字符
或遇到'\0'为止
,
比较结果由函数值带回:
①如果字符串1与字符串2相同,则函数值为0
②如果字符串1>字符串2,则函数值为一个正整数
③如果字符串1<字符串2,则函数值为一个负整数
如:
#include <stdio.h>
void main()
{
char str1[]={"Bejing"},str2[]={"China"};
printf("%d",strcmp(str1,str2));
}
-1
6️⃣strlen测字符串长度函数
语法:strlen(字符串数组)
作用:测字符串长度,函数的值为字符串中的实际值
如:
#include <stdio.h>
void main()
{
char str1[]={"Bejing"};
printf("%d",strlen(str1));
}
6
7️⃣strlwr转换为小写函数
语法:strlwr(字符串)
8️⃣strupr转换为大写函数
语法strupr(字符串)
字符数组举例
例1、输入一行字符,统计其中有多少个单词,单词之间用空格分隔开
#include <stdio.h>
void main()
{
char a[20];
int i,world,sum=1;
gets(a);
for(i=0;a[i]!='\0';i++){
if(a[i]==' '){
world=0;
}else if(world==0){
sum++;
world=1;
}
}
printf("单词个数%d",sum);
}
l am a boy
单词个数4
六、函数
函数就是功能。每一个函数用来实现一个特定的功能。函数的名字反映其代表的功能,完成一定功能的模块(算法),实现代码的复用
例1、输出以下结果,用函数调用实现
#include <stdio.h>
void main()
{
void star();
void word();
star();
word();
star();
}
void star(){
printf("***********************\n");
}
void word(){
printf("How do you do!\n");
}
***********************
How do you do!
***********************
函数分类:
(1)用户使用角度①库函数②用户自己定义的函数
(2)函数的形式角度①无参函数②有参函数
1、函数的定义
定义函数包括以下内容:
- 指定函数的名字
- 指定函数类型
- 指定函数的参数的名字和类型,以便在调用函数时向它们传递数据
- 指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能
定义函数的方法:
分为函数首部(函数头),函数体两部分
语法:
返回值类型(函数类型) 函数名(形参表列)--------函数首部
{
声明部分(变量定义或函数声明) --------函数体
执行部分(语句)
}
注意:
- 返回值类型(
函数的输出
)的确定,首先判断函数是否需要返回一个值,如果需要再看返回值的类型,如果没有返回值,则明确声明为void,一定注意,返回值只能有一个,如果多于一个,只能通过别的方式带回,此时也将函数返回值类型声明为void
- 函数名:一个合法的标识符,尽量见名知意,多个单词连用,通常除了第一个单词外,
其余单词首字母大写
- 形参表列:形参1类型 形参1名,形参2类型 形参2名….,一个函数可以有多个形参,形参之间用逗号隔开,形参名是任意合法的标识符。是函数完成其功能时,所需要的必要的已知条件,
相当于函数的输入
,如果函数不需要已知条件,则空着,此时是无参函数。 - 函数体,是函数功能的具体实现(算法的具体实现),
此时形参相当于已经有值(在函数调用时,由实参传递过来的值)
,不需要重新赋值,如果函数需要返回一个值,用return 语句带回(return语句将后面表达式的值带回到函数调用处,并结束函数的调用,return后面可以不加表达式,此时仅仅结束函数的调用,return语句可以有多个,但只有一个会执行)
例1、求两个数中的最大值
int max(x,y){
return x>y?x:y;
}
例2、求两个数的最大公约数
例3、求两个数的最小公倍数
2、函数的调用
1、函数调用的形式
语法:
函数名(实参表列)
注意:
- 实参表列,实参可以是常量,变量或表达式。要求实参的个数和形参一致,类型和形参赋值兼容(形参1=实参1),实参必须有确定的值
- 如果要使用函数的返回值,可以定义一个和函数返回值类型一致的变量,接收返回值
(1)函数调用语句
把函数调用单独作为一个语句,不要求函数带回值,只要求函数完成一定的操作
(2)函数表达式
函数调用出现在另一个表达式中,如:c=max(a,b),max(a,b)是一次函数的调用,他是赋值表达式中的一部分。这时要求函数带回一个确定的值以参加表达式的运算
(3)函数参数
函数调用作为另一个函数调用时的实参,如:m=max(a,max(b,c));
2、函数调用时的数据传递
(1)形式参数和实际参数
1)定义函数
时函数名后面括号中的变量名称为形式参数
2)在主调函数中调用一个函数
时,函数名后面括号中的参数为实际参数。实际参数可以是常量、变量或表达式
(2)实参和形参间的数据传递
调用过程中,系统会把实参的值传递给被调用函数的形参
例1、输入两个整数,求出其中较大者
#include <stdio.h>
void main()
{
int a,b;
int max(x,y);
scanf("%d%d",&a,&b);
printf("较大的数是:%d",max(a,b));
}
int max(int x,int y){
return x>y?x:y;
}
5 8
较大的数是:8
注意:
①在发生函数调用时,首先程序执行的流程转到被调用函数,会给被调用函数的局部变量
(定义在函数内部的变量,包括形参)分配内存空间
,会将实参的值对应地传给形参
②在函数调用结束时(return语句
或遇到函数体结束的右括号
),程序执行的流程转到主调函数中函数调用处
,回收被调用函数的局部变量
(定义在函数内部的变量,包括形参)所占内存空间
,如果需要return返回1个值,则把这个值带回函数调用处
因此,除了main函数外,其他函数中的局部变量只在被调用期间占有内存空间,不同函数的局部变量可以重名,仅仅是重名,一定占用不同的内存空间
(3)函数调用的过程
1)在定义函数中指定的形参,在未出现函数调用时,
它们并不占内存
中的存储单元。在函数发生调用时
,函数的形参才被临时分配存储单元
2)将实参的值传给对应的形参
3)利用形参的值进行运算
4)通过return语句将函数值带回到主调函数。注意返回值类型与函数类型一致,如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型
5)调用结束,形参单元被释放
。实参单元仍保留并维持原值
,没有改变。如果在执行一个被调用的参数时,形参的值发生改变,不会改变主调函数的实参的值。因为形参和实参是两个不同的存储单元
(4)函数的返回值
1)函数的返回值是通过函数中的return语句获得的
2)函数值的类型 如:int max(float x,float y)
3)在定义函数时指定的函数类型
一般应该和return语句中的表达式类型
一致。如果函数值得类型和return语句中表达式的值不一致,则以函数类型为准。对数值数据,可以自动进行类型转换。即函数类型决定返回值得值
。如:int max(float x,float y) 返回int值
4)对于不带回值得函数,应当用定义函数为void类型
3、函数的声明
(1)首先被调用的函数必须是已经定义的而函数
(2)如果使用库函数,应该在本文件开头使用#include指令将调用有关库函数时所需用到的信息包含到本文件中来
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面,应该在主调函数中对被调用的函数作声明。声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统
,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。
例2、输入两个实数求和
#include <stdio.h>
void main()
{
float a,b;
float add(float x,float y);
scanf("%f%f",&a,&b);
printf("%f",add(a,b));
}
float add(float x,float y){
return x+y;
}
1.2 1.5
2.700000
函数声明(函数原型声明):当被调用函数写在主调函数之后,此时可以在主调函数中,对被调用函数进行声明,方便编译系统检查函数调用表达式的正确性(检查项包括函数的类型、函数名字、参数的类型、参数个数和参数的顺序)。如果被调用函数写在主调函数之前,此时函数声明可以省略。
语法:
(1)函数返回值类型 函数名(形参1的类型 形参1,形参2类型 形参2);
如: float add(float x,float y);
(2)函数返回值类型 函数名(形参1的类型,形参2的类型…);
如:float add(float,float);
4、函数的嵌套调用
C语言的函数定义是相互平行、独立的,也就是说,在定义函数时,一个函数内不能再定义另一个函数,即不能嵌套定义,但可以嵌套调用函数,即在调用函数的过程中,又调用另一个函数。
例3、输入4个整数,找出其中最大的数。用函数的嵌套调用来处理
#include <stdio.h>
void main()
{
int a,b,c,d;
int max4(int a,int b,int c,int d);
scanf("%d%d%d%d",&a,&b,&c,&d);
printf("%d",max4(a,b,c,d));
}
int max4(int a,int b,int c,int d)
{
int max;
int max2(int x,int y);
max=max2(a,b);
max=max2(max,c);
max=max2(max,d);
return max;
}
int max2(int x,int y)
{
return x>y?x:y;
}
5、函数的递归调用
在调用一个函数的过程中又出现直接或间接的调用该函数本身,称为函数的递归调用
例4、用递归的方式求n!
#include <stdio.h>
void main()
{
int a;
int fun(int x);
scanf("%d",&a);
printf("%d",fun(a));
}
int fun(int x)
{
int t;
if(x==1||x==0)
t=1;
else
{
t=x*fun(x-1);
}
return t;
}
七、指针
指针存在的意义:通过指针来改变变量(变量的本质是某一块内存空间的别名)的值,即改变内存空间的值。
1、什么是指针
1、什么是地址
在程序中定义了一个变量,在对程序进行编译时,系统会给这个变量分配内存单元。根据变量的类型,分配一定长度的空间(如:Visual C++ 为整型分配4个字节)。内存中每一个字节有一个编号,这就是地址
。
C语言中的地址包括内存编号
和数据类型
2、什么是指针
由于通过地址能找到所需变量单元,可以说,地址指向该变量单元。因此,将地址形象化地称为指针(通过它能找到以它为地址的内存单元)。
3、直接访问
通过变量名
找到相应的地址,从字节中按照不同的存储方式读出变量的值
4、间接访问
将变量的地址存放在另一变量中,然后通过该变量来找到变量的地址,从而访问变量值
如:i_pointer=&i //将i的地址存放在i_pointer中
一种想法:
int a,b;
b=10;
a=&b;//把b的地址存放在整型变量中,虽然可以存储,但是不能通过存储的地址访问数据
将数值3送到变量i中,有两种表达方法
(1)将3直接送到变量 i 所标识的单元中,如:i=3
(2)将3送到变量i_pointer所指向的单元,如:*i_pointer=3,其中 *i_pointer表示i_pointer指向的对象
注意:区分“指针”和“指针变量” 。如:可以说变量 i 的指针是2000,而不能说 i 的指针变量是2000。指针是一个地址指针变量是存放地址的变量
2、指针变量
例1、通过指针访问整型变量
#include <stdio.h>
void main()
{
int a=100,b=10;
int *x,*y;
x=&a;
y=&b;
printf("%d %d\n",a,b);
printf("%d %d",*x,*y);
}
1、定义指针变量
语法:类型名 * 指针变量名
注意:
①指针变量前面的 “*” 表示该变量为指针型变量
②定义指针变量必须指定基类型。不同数据类型的数据在内存中所占的字节数
和存放方式
是不同的
int a;
float *b;
b=&a;//这样写不对
③一个变量的指针的含义包括两个方面,一是以内存单元编号表示的纯地址,一是它指向的存储单元的数据类型
④指向整型数据的指针类型表示为“ int * ”
⑤指针变量中只能存放地址,不能将一个整数赋给一个指针变量
如:* a=100; //不合法
2、引用指针变量
(1)给指针变量赋值,如 :p=&a; //指针变量p的值时变量a的地址
(2)引用指针变量指向的变量,如:pintf(“%d”,*p);//输出变量a的值
如果有 *p=1;//将1赋给p当前所指向的变量,即a=1
(3)引用指针变量的值,如:printf(“%o”,p);//以八进制输出p的地址
例2、输入两个整数,按先大后小输出(指针方法)
不交换整型变量的值,交换两个指针的值
注意:此时改变的是地址,而不是指向的数据
#include <stdio.h>
void main()
{
int a,b,*x,*y,*t;
scanf("%d%d",&a,&b);
x=&a;
y=&b;
if(*x<*y)
{
t=x;x=y;y=t;//x=&b;y=&a;
}
printf("%d %d\n",a,b);
printf("max=%dmin=%d",*x,*y);
}
5 9
5 9
max=9min=5
3、指针变量作为参数函数
函数的参数不仅可以是整型、浮点型、字符型还可以是指针数据。它的作用是将一个变量的地址传送到另一个函数中。
例3、输入两个整数,按先大后小输出。用函数处理,指针类型数据作为参数
注意:此时对指针指向的数据进行修改
#include <stdio.h>
void main()
{
void swap(int * x,int * y);
int a,b;
scanf("%d%d",&a,&b);
swap(&a,&b);
printf("max=%dmin=%d",a,b);
}
void swap(int * x,int * y)
{
int t;
if(*x<*y)
{
t=*x;
*x=*y;
*y=t;
}
}
5 9
max=9min=5
不能企图通过改变指针形参的值而使指针实参的值改变,如下:
#include <stdio.h>
void main()
{
void swap(int * x,int * y);
int a,b;
scanf("%d%d",&a,&b);
swap(&a,&b);
printf("max=%dmin=%d",a,b);
}
void swap(int * x,int * y)
{
int *t;
if(*x<*y)
{
t=x;
x=y;
y=t;
}
}
5 9
max=5min=9
3、通过指针引用数组
1、数组元素的指针
一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素。所谓数组元素的指针就是数组元素的地址
。
引用数组可以用下标法
,如:a[3],可以用指针法
,即通过指向数组元素的指针找到所需的元素。
指针法特点:使目标程序质量高(占内存少,运行速度快)
数组名代表数组中首元素地址,如:p=&a[0];等价于p=a;
2、引用数组元素时指针的运算
在指针已指向一个数组元素时,可以对指针进行以下运算:
(1)加(减)一个整数
(2)自加(减)运算
1️⃣如果指针变量p已指向数组中的一个元素,这p+1指向同一数组中的下一个元素
,p - 1指向同一数组中的上一个元素
2️⃣如果p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址
。实际地址为p+1乘数据类型
3️⃣ *(p+i)和 *(a+i)是p+i和a+i指向的数组元素,及a[i]
4️⃣指针变量p1和p2指向同一数组中的元素,执行p1-p2,结果是(p1-p2)/数组元素的长度。如:(2020-2012)/4=2
,这个结果是由意义的,表示p2所指的元素与p1所指的元素之间差几个元素
.两个地址相加没有意义
3、通过指针引用数组元素
(1)下标法,如:a[i]
(2)指针法,如:*(a+i)
例4、输出数组中全部值
(1)下标法(2)通过数组名计算数组元素地址,找出元素值(3)指针法
#include <stdio.h>
void main()
{
int a[]={1,2,3,4,5,6,7,8,9,10};
int i,*p;
for(i=0;i<10;i++)
{
printf("%d",a[i]);//1
}
printf("\n");
for(i=0;i<10;i++)
{
printf("%d",*(a+i));//2
}
printf("\n");
for(p=a;p<(a+10);p++)
{
printf("%d",*p);//3
}
}
第一种第二种效率相同,第三种比前两种快,提高了执行效率
注意:数组名a代表数组首元素的地址,它是一个指针型常量
,它的值在程序运行期间是固定不变的,所有a++是错误的
例5、通过指针变量输出整型数组a的10个元素
#include<stdio.h>
void main()
{
int a[10],i,*p=a;
for(i=0;i<10;i++)
scanf("%d",p++);
p=a;//此时p已经指向最后一位,必须重新赋值
for(i=0;i<10;i++,p++)
printf("%d\n",*p);
}
注意:①指针变量 p 可以指向数组以后的存储单元,虽然编译不会出错,但是运行结果无法预期,所有要避免
②指向数组元素的指针变量可以带下标,如p[ i ] 转换为 *(p+i)
③*p++;
。由于++和 * 同优先级,结合方向为自右向左,因此它等价于 *(p++)++后置的时候,本身含义就是先运算后增加1(运算指的是p++作为一个整体与前面的 * 进行运算;增加1指的是p+1)。先引用 *p 的运算,然后再使p加1
④ *(p++)和 *(++p)是不同的
⑤ (*p)++ 与 ++(*p)
(*p)++,使用()强制将 * 与p结合,只能先计算 * p,然后对 *p整体的值++
++(*p),先 * p取值,再前置++,[该值+1后]作为整个表达式的值。
4、用数组名做函数参数
语法:
#include<stdio.h>
void main()
{
void fun(int arr[],int n);//函数声明
int array[10];//数组定义
//省略
fun(array,10);//用数组名作为函数参数
}
void fun(int arr[],int n)//定义函数
{
//省略
}
当用数组名作为参数时,如果形参数组中各个元素发生变化,实参数组元素的值随之发生变化
(1)数组元素作为实参
。定义一个函数 swap(int x,int y);调用swap(a[0],a[1]);当x,y改变,数组值不变
(2)数组名作为实参
。因为数组名代表该数组首地址,而形参是用来接收从实参传递过来的数组首地址,所以形参应该是一个指针变量
(只有指针变量才能够存放地址)。定义函数fun(int arr[],int n)相当于fun(int * arr,int n)
注意:实参数组名代表一个固定地址,或者说是指针常量,但形参数组名并不是一个固定地址,而是按指针变量处理。
例6、将数组a中n个整数按相反顺序存放
实参用数组名a,形参可用数组名,也可用指针变量
1、形参为数组名
#include<stdio.h>
void main()
{
void swap(int arr[],int n);
int a[10],i;
for(i=0;i<10;i++)
scanf("%d",&a[i]);
swap(a,10);
for(i=0;i<10;i++)
printf(" %d",a[i]);
}
void swap(int arr[],int n)
{
int i,t;
for(i=0;i<=(n-1)/2;i++)
{
t=arr[i];
arr[i]=arr[n-1-i];
arr[n-1-i]=t;
}
}
1 2 3 4 5 6 7 8 9 10
10 9 8 7 5 6 4 3 2 1
2、形参为指针
#include<stdio.h>
void main()
{
void swap(int * arr,int n);
int a[10],i;
for(i=0;i<10;i++)
scanf("%d",&a[i]);
swap(a,10);
for(i=0;i<10;i++)
printf(" %d",a[i]);
}
void swap(int *arr,int n)
{
int *i,*j,*p,t,m;
m=(n-1)/2;//循环次数4
p=arr+m;//a[4]
i=arr;//起始值,数组首地址 a[0]
j=(arr+n-1);//最后值,数组个数-1 a[9]
for(;i<=p;i++,j--)
{
t=*i;
*i=*j;
*j=t;
}
}
归纳分析
(1)形参和实参都用数组名
如:
void main()
{
int a[10];
fun(a,10);
}
void fun(int x[],int n)
{
}
在函数调用期间,形参与实参共用一段内存单元
(2)实参用数组名,形参用指针
void main()
{
int a[10];
fun(a,10);
}
void fun(int *x,int n)
{
}
通过x值得改变可以指向数组a的任一元素
(3)实参和形参都用指针变量
void main()
{
int a[10],*p;
p=a;
fun(a,10);
}
void fun(int *x,int n)
{
}
通过x值得改变可以指向数组a的任一元素
(4)实参为指针变量,形参为数组名
void main()
{
int a[10],*p;
p=a;
fun(a,10);
}
void fun(int x[],int n)
{
}
对例6修改,形参和实参为指针
#include <stdio.h>
void main()
{
void swap(int * arr,int n);
void swap(int *arr,int n);
int a[10],i,*p=a;
for(i=0;i<10;i++,p++)//通过指针变量自增访问数组元素
scanf("%d",p);//p为地址,所以不用加&符
swap(a,10);
p=a;//注意要重新赋值
for(i=0;i<10;i++,p++)
printf("%d ",*p);
}
void swap(int *arr,int n)
{
int *i,*j,*p,t,m;
m=(n-1)/2;//循环次数4
p=arr+m;//a[4]
i=arr;//起始值,数组首地址 a[0]
j=(arr+n-1);//最后值,数组个数-1 a[9]
for(;i<=p;i++,j--)
{
t=*i;
*i=*j;
*j=t;
}
}
例7、用指针方法对10个整数按照由大到小排序
#include <stdio.h>
void main()
{
void sort(int x[],int n);
int a[10],i,*p=a;
for(i=0;i<10;i++)
scanf("%d",p++);
sort(a,10);
p=a;
for(i=0;i<10;i++)
printf("%d ",*p++);
}
void sort(int x[],int n)
{
int i,j,t;
for(i=0;i<n-1;i++)//外层循环0-8,9次
{
for(j=0;j<n-i-1;j++)//第一次内循环比较次数9每次-1
{
if(x[j]<x[j+1])
{
t=x[j];
x[j]=x[j+1];
x[j+1]=t;
}
}
}
}
5、通过指针引用字符串
1、字符串的引用方式
(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中的一个字符,也可以通过数组名和格式声明%s输出该字符串
如:
#include <stdio.h>
void main()
{
char string[]="l love China";
printf("%s\n",string);
printf("%c\n",string[7]);
}
l love China
C
(2)用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量
如:
#include <stdio.h>
void main()
{
char * string="l love China";//定义字符指针变量string并初始化
printf("%s\n",string);
}
l love China
对字符串指针变量string初始化,实际上是把字符串第一个元素的地址(即存放字符串的字符串数组的首地址)赋给指针变量string,使string指向字符串的第一个字符
注意:
①在C语言中只有字符变量,没有字符串变量
②在定义char * string="l love China";
时不是吧“l love China”这些字符串存放到string中(指针变量只能存放地址),只是把“l love China”的第一个字符的地址赋给指针变量string
③可以对指针变量进行再赋值,string="l am a student";
此后string就指向“l am a student”的第一个字符,不能再引用“l love China”了
④printf("%s",string);
系统会输出string所指向的字符串第一个字符,然后自动使string+1指向下一个字符,再输出字符,往复直到遇到'0'
为止
例8、将字符串a,复制为字符串b,然后输出字符串b
数组下标法:
#include <stdio.h>
void main()
{
char a[]="l love China",b[20];
int i;
for(i=0;*(a+i)!='\0';i++)
{
*(b+i)=*(a+i);
}
*(b+i)='\0';
printf("%s\n",a);
printf("%s\n",b);
}
指针法:
#include <stdio.h>
void main()
{
char a[]="l love China",b[20];
int i;
char *p1,*p2;
p1=a;p2=b;
for(i=0;*(p1+i)!='\0';i++)
{
*(p2+i)=*(p1+i);
}
*(p2+i)='\0';
printf("%s\n",a);
printf("%s\n",b);
}
八、结构体
1、结构体类型的定义
1、自己建立结构体类型
用户自己建立由不同类型数组组成的组合型的数据结构,它成为结构体
语法:struct 结构体名 {成员表列};
(1)struct是声明结构体类型时必须使用的关键字,不能省略
(2)结构体名是由用户指定的,以区别于其它结构体类型
(3)花括号内是该结构体所包括的子项,成为结构体的成员
成员可以属于另一个结构体类型
如:
struct Date
{
int year;
int month;
int day;
};
struct Student
{
int num;
char name[20];
char sex;
int age;
struct Date birthday;//成员birthday属于struct Date类型
char addr[30];
};
2、定义结构体类型变量
前面只是建立了一个结构体类型,它相当于一个模型,并没有定义变量,其中并无数据,系统对之也不分配存储单元。
-
先声明结构体类型,再定义该类型的变量
如:struct Student student1,student2;
在定义了结构体变量后,系统会为之分配存储单元。根据结构类型中包含的成员情况 Student (4+20+1+4+12+30=71) -
在声明类型的同时定义变量
语法:struct 结构体名 {成员列表}变量名列表;
如:struct Student { int num; char name[20]; char sex; int age; float score; char addr[30]; } student1,student2;
-
不指定类型名而直接定义结构体类型变量
语法:struct {成员列表}变量名列表;
注意:
①结构体类型与结构体变量名是不同概念,在编译时,对类型不分配空间,只对变量分配空间
②结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象
③对结构体类型中的成员,可以单独使用,它的作用与地位相当于成员变量
3、结构体变量的初始化和引用
在定义结构体变量时,可以对它初始化,即赋予初始值,然后引用这个变量
例1、把一个学生的信息(包括学号、姓名、性别、住址)放在一个结构体变量中,然后输出这个学生的信息
#include <stdio.h>
void main()
{
struct Student
{
int num;
char name[20];
char sex;
char addr[30];
} student1={111,"liu wei",'M',"wei hai"};
printf("%d %s %c %s",student1.num,student1.name,student1.sex,student1.addr);
}
111 liu wei M wei hai
(1)是对结构体变量的初始化,而不是对结构体类型的初始化
C99标准允许对某一成员初始化,如:struct Student studnet1={.num=1}
.num隐含代表结构体变量student1中的成员变量student1.num
(2)可以引用结构体变量的值,如:结构体变量名.成员名
(3)如果成员本身又属于一个结构体类型,则要用若干个成员变量符,一级一级地找到最低的一级的成员。只能对最低级的成员进行赋值或存取以及运算。如:student1.birthday.year;
(4)对结构体变量的成员可以像普通变量一样进行各种运算
(5)同类型的结构体变量可以相互赋值,如student1=student2;
(6)可以引用结构体变量成员的地址,也可以引用结构体变量的地址,如: scanf("%d",&student1.num); printf("%o",&student1);
结构体变量的地址主要用作函数的参数,传递结构体变量的地址
例2、输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩
#include <stdio.h>
void main()
{
struct Student
{
int num;
char name[20];
float score;
} student1,student2;
printf("学生1\n");
scanf("%d%s%f",&student1.num,student1.name,&student1.score);
printf("学生2\n");
scanf("%d%s%f",&student2.num,student2.name,&student2.score);
if(student1.score>student2.score)
printf("%d %s %.2f",student1.num,student1.name,student1.score);
else if(student1.score<student2.score)
printf("%d %s %.2f",student2.num,student2.name,student2.score);
else
{
printf("%d %s %.2f\n",student1.num,student1.name,student1.score);
printf("%d %s %.2f\n",student2.num,student2.name,student2.score);
}
}
学生1
100 liuwei 90
学生2
200 liuwe 100
200 liuwe 100.00
2、使用结构体数组
1、定义结构体数组
语法:
(1)struct 结构体名{成员列表} 数组名[数组长度];
(2)结构体类型 数组名[数组长度]={初值列表}
例3、有3个候选人,每个选民只能投票选一人,要求编写一个统计选票的程序,先后输入被选人的名字,输出各个人的得票情况
#include <stdio.h>
#include <string.h>
struct Person
{
char name[20];
int count;
} leader[3]={"li",0,"liu",0,"wang",0};
void main()
{
char a[30];
int i,j;
for(i=1;i<=5;i++)
{
scanf("%s",a);
for(j=0;j<3;j++)
{
if(strcmp(a,leader[j].name)==0) leader[j].count++;
}
}
for(i=0;i<3;i++)
{
printf("%s %d\n",leader[i].name,leader[i].count);
}
}
li
liu
li
wang
li
li 3
liu 1
wang 1
3、结构体指针
结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的地址存放在一个指针变量中,那么这个指针变量就指向该结构体变量
1、指向结构体变量的指针
指向结构体对象的指针变量既可以指向结构体变量,也可指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型相同。
如:struct Student *p;//p可以指向struct Student 类型的变量或数组元素
例4、通过指向结构体变量的结构体指针变量输出结构体变量中的成员
#include <stdio.h>
void main()
{
struct Student
{
int num;
char name[20];
char sex;
char addr[30];
};
struct Student stu1={1010,"liu wei",'M',"wei hai"};
struct Student *pt;
pt=&stu1;
printf("%d %s %c %s\n",stu1.num,stu1.name,stu1.sex,stu1.addr);
printf("%d %s %c %s\n",(*pt).num,(*pt).name,(*pt).sex,(*pt).addr);
}
1010 liu wei M wei hai
1010 liu wei M wei hai
注意:* p两侧的括号不可省略,因为成员运算符“.”优先于“*”运算符,*p.num就等价于 * (p.num)了
如果p指向一个结构体变量stu,一下3种用法等价
(1)stu.成员名,如:stu.num;
(2)(*p).成员名,如:(*p).num;
(3)p->成员名,如:p->num;
2、指向结构体数组的指针
例5、有3个学生的信息,放在结构体数组中,要求输出全部学生的信息
#include <stdio.h>
#include <stdio.h>
void main()
{
struct Student
{
int num;
char name[20];
char sex;
char addr[30];
};
struct Student stu1[3]={1010,"liu wei",'M',"wei hai",
1011,"li na",'M',"beijing",
1012,"jiayinghan",'M',"tai an"};
struct Student *pt;
pt=stu1;
for(;pt<stu1+3;pt++)
printf("%d %s %c %s\n",pt->num,pt->name,pt->sex,pt->addr);
}
1010 liu wei M wei hai
1011 li na M beijing
1012 jiayinghan M tai an
注意:
(1)如果p的初值为stu,即指向stu的序号为0的元素,p+1后,p就指向下一元素,如:
(++p)->num;//先使p+1(即stu1[1]),然后得到p指向的num值(即1011)
(p++)->num;//先得到p指向的num值(即1010),然后使p+1(即stu1[1])
(2)程序定义的p是一个指向struct Student类型的指针变量,不能存储其他类型的地址,如:p=stu1[0].name;//stu1[0].name是stu[0]元素的成员变量name的首字符的地址
可以强转后赋值,如:p=(struct Student*)stu1[0],name;
3、用结构体变量和结构体变量的指针作函数参数
(1)用结构体变量的成员作参数
(2)用结构体变量作参数
(3)用结构体变量的指针作参数