笔记目录
指针
指针使用灵活,功能强大,是C语言的灵魂。指针与底层硬件联系紧密
使用指针可操作数据的地址,实现数据的间接访问。
计算机存储机制
int a=0x12345678;//int型变量占四个字节
short b=0x5A6B;//short型变量占两个字节
char c[]={0x33,0x34,0x35};//char型数组每个元素占一个字节,从上往下存储
插图
定义指针
指针变量用于存放其它结构单元(变量/数组/结构体/函数)的首地址。若指针存放了某个数据单元的首地址,则这个指针指向了这个数据单元,若指针存放的值是0,则这个指针为空指针。
定义一个指针变量:
放图
16位系统:x=2, 32位系统:x=4, 64位系统:x=8
指针的操作
若已定义
int a;//定义一个int型数据
int *p;//定义一个指向int型数据的指针
操作方式 | 举例 | 解释 |
---|---|---|
取地址 | p=&a; | 将a的首地址赋值给p |
取内容 | *p | 使指针向下移动一个数据宽度 |
加 | p++; p+=5; ; | 使指针向下移动1个数据宽度 使指针向下移动5个数据宽度 |
减 | p--; ;p-=5 ; | 使指针向上移动1个数据宽度 使指针向上移动5个数据宽度 |
注意:int *p=&a
//合法,因为此处的*
不是取内容运算,而是表示指针类型
指针的应用
传递参数
-
使用指针传递大容量参数(如数组),主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用
-
使用指针传递输出参数,利用主函数和子函数同一套数据的特性,实现数据的返回,可实现多返回值函数的设计。
传递返回值
- 将模块的公有部分返回,让主函数持有函数的“句柄”,便于程序向指定对象的操作
注意事项
- 在对指针取内容时,确保指针指在合法的位置,否则将会导致程序出现不可预知的错误
- 同级指针才能相互赋值,跨级赋值将会导致编译器报错或警告
上图
指针与数组
数组是一些相同类型变量组成的集合,其数组名即为指向该数据类型的指针。数组的定义等效于申请内存、定义指针和初始化
例如:char c[]={0x33,0x34,0x35};
等效于:
- 申请内存
- 定义
char *c=0x4000\\首元素地址;
- 初始化数组数据
//int a[]={0x33,0x34,0x35,};
int *a
a=malloc(3*4);
*a=0x33;
*(a+1)=0x34;
*(a+2)=0x35;//代码与第一行的注释完全等效
利用下标引用数据也等效于取指针内容。
例如:
c[0];
等效于:*c;
c[1];
等效于:*(c+1);
c[2];
等效于:*(c+2);
通过指针引用多维数组
假如定义了一个二维数组int a[3][4];
二维数组可以看作由多个一维数组组成,因此a[0],a[1],a[2]
可以看作一维数组名,也是一维数组的首地址。
a[1]==a+1
那要如何得到每个”一维数组“后面元素的地址呢?答案如下:
&a[0][1]==a[0]+1
所以a[0][1]==*(a[0]+1)==*((a+0)+1)
定义一个指向一维数组(行)的指针
分析以下程序:
int a[4]={34,45,26,54};
int (*p)[4];定义一个能够指向一行含有4个元素的一维数组的指针p
p=&a;注意&不能漏
printf("%d",(*p)[3]);输出第三个元素
注意:不能写成p=a,因为a为a[0]的地址,即&a[0],只有p=&a才能表示p指向”一行数组“
定义一个指向二维数组的指针
例题:用指针输出一个二维数组
代码如下:
#include<stdio.h>
int main()
{
int a[2][4]={1,2,3,4,5,6,7,8};
int (*p)[4]=a;//指向二维数组的第0行
int i,j;
printf("Please put in the row andvthe column\n");
scanf("%d %d",&i,&j);
printf("%d",*(*(p+i)+j));//*(p+i)为第i行数组的首地址
return 0;
}
用指向数组的指针做函数参数
例题:有一个班,有三名学生,各学四门科目,输出平均总成绩,输入一个学生的学号来获得他的所有成绩。
#include<stdio.h>
int main()
{
void average(float *p,int n);
void search(float(*p)[4],int n);
float score[3][4]={34,23,43,66,54,35,75,34,75,35,76,73,};
average(*score,12);//传入首元素的地址
search(score,2);//二维数组名表示首行地址
return 0;
}
void search(float(*p)[4],int n)
{
int i;
for(i=0;i<4;i++){
printf("%5.2f ",*(*(p+n)+i));
}
}
void average(float*score,int n){
float sum=0;
int i=0;
for(i=0;i<n;i++){
sum+=*score++;
}
printf("%5.2f\n",sum/n);
}
输出:51,92
75.00 35.00 76.00 73.00
通过指针引用字符串
例题:将字符串a复制为字符串b,通过指针实现。
#include<stdio.h>
int main()
{
char*a="hello world";
char b[20];
char*p=a,*q=b;
int i=0;
while(*q++=*p++);//这句非常简练,先对*p赋值,然后两个指针同时下移,再判断赋值是否为‘0’,以此循环。
puts(b);
return 0;
}
输出结果:hello world
用函数来实现功能如下:
#include<stdio.h>
void copy(char *p,char *q)
{
while(*q++=*p++);
}
int main()
{
char*a="hello world";
char b[20];
char*p=a,*q=b;
int i=0;
copy(p,q);
puts(b);
return 0;
}
输出结果:hello world
指向函数的指针
如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段存储空间有一个起始地址,也称为该函数的入口地址。调用函数时,从函数名得到该函数的起始地址,并执行函数代码。
函数名就是函数的指针,它代表函数的起始地址。
可以定义一个指针变量来存放某一函数的起始地址,这就意味着此指针指向该函数。例如:
int(*p)(int int);
定义p是一个指向函数的指针变量,它可以指向一个输出类型为int型,且有两个int型参数的函数。此时,p的类型用int(*)(int int)表示。
例题:有两个整数a,b,由用户输入1,2或3。如输入1,输出a,b中的大者;如输入2,输出a,b中的小者;如输入3,输出a+b的和。
代码如下:
#include<stdio.h>
void fun(int a,int b,int(*p)(int,int))
{
printf("%d",(*p)(a,b));
}
int plus(int a,int b)
{
return a+b;
}
int max(int a,int b){
int max=a;
if(a<b)max=b;
return max;
}
int min(int a,int b){
int min=b;
if(a<b){
min=a;
}
return min;
}
int main()
{
int a,b,sign;
scanf("%d %d %d",&a,&b,&sign);
if(sign==1)fun(a,b,plus);
if(sign==2)fun(a,b,max);
if(sign==3)fun(a,b,min);
return 0;
返回指针值的函数
定义:int *a(int x,int y);
函数a能返回一个指针值。
例题:有3个学生,每个学生有4门课程的成绩。要求用户在输入学生序号后,能输出该学生的全部成绩。
结构体
结构体类型属于构造类型,它用来描述类型不同的一组数。
结构体的定义
结构体类型的一般定义格式如下:
struct worker//结构体名
{
char name[9];//姓名
char sex;//性别
int age;//年龄
float salary;//工资
};//注意分号
结构体类型的特点
- 一个结构体类型有其专用的标志,它由两个标识符组成。如上例中的
struct worker
为结构体类型名。 - 结构体的每一个数据项被称为成员,也成为[域]。可以定义与结构体同名的成员。
注意:关于结构体在内存中的存储,在定义变量后,计算机会依据结构体成员的数据类型,在内存中取得一块连续的存储空间来存放这些成员。
比如:
struct worker w1;
printf("%d",sizeof(w1));
运行结果为sizeof(char[9])+sizeof(char)+sizeof(int)+sizeof(flout)=9+1+4+4=18
结构体变量的定义
结构体在使用时,需要先定义结构体类型,再根据自定义的结构体类型去定义结构体变量。有以下三种方法定义一个结构体变量:
1.如果上面已经定义了结构体类型struct worker,直接用它定义结构体变量
struct worker w1,w2;
一个结构体变量所占内存长度是各成员所占内存长度之和
2. 定义一个结构体类型的同时定义该类型的变量
struct worker
{
char name[9];
char sex;
int age;
float salary;
}w1,w2;//一种紧凑的定义方式
如果需要,下次还可以用此类型定义其它变量 struct w3,w4;
- 直接定义结构体变量,即不出现结构体名
struct //没有结构体名
{
char name[9];
char sex;
int age;
float salary;
}w1,w2;
由于没有结构体名,故无法再定义该类型的结构体变量。
说明:
1.只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。
2.对于某个具体的结构体类型,成员数量必须固定。
3.对结构体中的成员可以单独使用,其作用与地位相当与普通变量。
4.结构体类型的成员也可以是一个结构体变量。例如:
struct date
{
int month;
int day;
int year;
};
struct worker
{
char name[9];
char sex;
struct date birthday;//以struct date型变量为成员
float salary;
}w1,w2;
也可以事先不定义struct date,而再定义struct worker时定义
struct worker
{
char name[9];
char sex;
struct date{//
int month;
int day;
int year;
}birthday;
float salary;
}w1,w2;
这种结构体的说明中又包含结构体称为结构体的嵌套。
结构体变量的初始化
结构体类型的使用方式与数组是类似的,它的初始化需要以每个成员为基本单位,可以在定义变量时同时赋值。例如:
struct worker
{
char name[9];
char sex;
int age;
float salary;
};
struct worker w1={"LiMing",'M',30,1500};
c语言编译器会按照顺序将数据存储到结构体变量的成员相应的存储单元中。
结构体变量的引用。
- 引用单个成员
其中.
为分量运算符或成员运算符。例如:
struct worker
{
char name[9];
char sex;
int age;
float salary;
}w1;
strcopy(w1.name,"LiMing");
w1.age=30;
w1.salary=1500;
- 整体引用结构体变量。可以将一个结构体变量赋值给另一个相同类型的结构体变量。例如:
struct worker w1={"LiMing",'M',30,1500};
struct worker w2=w1;
- 结构体变量中成员的输入输出。C语言不允许把一个结构体变量作为一个整体输入或输出,例如下列语句是不合法的:
scanf("%d",&w1);
printf("%d\n",w1);
printf("%s,%c,%d,%f\n",w1);
要输入、输出一个结构体型变量,只能对其成员进行操作。
例如,可以用以下形式输入和输出:
scanf("%s,%c,%d,%f",w1.name,&w1.sex,&w1.age,&w1.salary);
printf("%s,%c,%d,%f",w1.name,w1.sex,w1.age,w1.salary);
当然,对w1.name的输入输出也可采用:
gets(w1.name);
puts(w1.name);
- 可以引用成员的地址。如:
scanf("%d",&w1.age);
printf("%o %o",&w1,&w2);//输出w1,w2的地址。srtuct
结构体数组
定义
和结构体变量类似
- 先定义结构体类型再定义结构体数组。例如:
struct worker
{
char name[9];
char sex;
int age;
float salary;
};
struct worker w[50];
以上定义了一个结构体数组w,它有50个元素,每一个元素都是struct worker类型的,这个数组在内存中占用连续的一段存储单元。
2.定义类型的同时定义数组。例如:
struct worker
{
char name[9];
char sex;
int age;
float salary;
}w[50];
- 直接定义结构体数组
struct //结构体类型未命名
{
char name[9];
char sex;
int age;
float salary;
}w[50];
这三种方法定义的效果相同。
结构体数组的引用
数组中的每一个变量都相当于一个结构体变量。有关数组的用法同一般数组。
例如,数组的输入可采用以下形式:
for(i=0;i<50;i++)
scanf("%s,%c,%d,%f,",w[i].name,w[i].sex,w[i].salary);
数组的输出:
for(i=0;i<50;i++)
printf("%s,%c,%d,%f",w[i].name,w[i].sex,w[i].salary);
结构体指针
定义
和定义结构体变量的方法相同,也有三种方式。
- 如前面定义过结构体类型,则可定义该类型的指针变量。例如:
struct worker *p,*q;
- 定义类型的同时定义指针变量。例如:
struct worker
{
char name[9];
char sex;
int age;
float salary;
}*p,*q;
- 直接定义指针变量。例如:
struct
{
char name[9];
char sex;
int age;
float salary;
}*p,*q;
结构体指针的使用
- 与普通指针相同,结构体指针也需要先赋值再使用。例如:
struct worker
{
char name[9];
char sex;
int age;
float salary;
};
struct worker w1,w[50],*p,*q;
p=&w1;//将结构体变量w1的首地址赋值给p
q=w;//或q=&w[0];将结构体数组的数组名,即首地址赋值给q
注意:结构体指针只能指向一个结构体变量,不能指向变量中的某一个成员,因为成员与结构体的数据类型是不一致的。
如下列赋值方式是错误的
p=&w1.name;q=&w[0].age;//非法
2. 利用结构体指针间接访问结构体成员。
- 直接访问:
w1.age
- 间接访问:
(*p).age
或p->age
注意:
- 运算符->
左侧只能是结构体指针变量
-(*p)=&w1.age
中的括号不能省,
例题
输入一个学生的学号、姓名、期中考试成绩和期末考试成绩,计算平均成绩并输出所有信息。
代码如下:
#include<stdio.h>
//定义结构体
struct student//命名结构体
{//定义成员
char num[10];
char name[10];
int mid;
int end;
double aver;
};
int main()
{
struct student *p,stu;//定义结构体变量与指针
p=&stu;//指针指向变量
scanf("%s%s%d%d",&stu.num,&stu.name,&stu.mid,&stu.end);//输入信息
(*p).aver=((*p).mid*1.0+(*p).end*1.0)/2;//计算平均成绩
printf("num:%s\n,name:%s\n",stu.num,stu.name);//打印信息与成绩
printf("mid score:%d\n,final score:%d\n,average score:%.1f\n",stu.mid,stu.end,stu.aver);
return 0;
}
运行结果如下: