例8-1. 密码开锁
几位同学去玩密室逃脱游戏,密室门上有一把四位数的数字密码锁,只有在密室中找到开锁密码才能走出密室。密室中整齐地摆放着规格大小完全相同的26个寄存箱,每个寄存箱上按顺序者了有一个英文字母和一个编号,字母从A到Z,编号从001到26.同学们在密室中认真搜寻,在角落里找到一把钥匙,用这把钥匙再打开编号为24的X寄存箱,里面有一张字条,上面写着"5324",用这四个数字去开密码锁,果然打开了,成功逃脱密室
关键点分析:
得到线索: 找到一把钥匙,打开
提示地址: 里面是一把
找到目标: 打开编号为24的
取出内容: "5342"
#include<stdio.h>
int main()
{
int x=5342; /* 变量级竽存放密码值5342 */
int *p=NULL; /* 定义整型指针变量p,NULL值为0,代表空指针 */
p=&x; /* 为指针变量赋值,将x的地址存放在指针变量p中 */
printf("If I know the name of the variable, I can get its value by names: %d\n", x);
printf("If I know the address of the variable is %x, then I also can get its value by address: %d\n", p, *p);
return 0;
}
int x=20, y=1, z=1;
printf("%d", x);
即将变量的地址放在另一个变量中,访问时需先找到该变量取出地址,再访问前者变量
int x=20; int *p=NULL; p=&x;
printf("%d", *p);
类型名表明指针变量
int *p; /* p是整型指针,指向整型变量 */
float *fp; /* fp是浮点型指针,指向浮点型变量 */
char *cp; /* cp是字符型指针,指向字符型变量 */
注意,在int *p;的定义中,指针变量名是
如果指针的值是某个变量的地址,可以通过指针间接访问该变量
& 取地址运算符,将变量的地址赋给指针变量
* 间接访问运算符,访问指针所指向的变量
int *p, a=3; /* 定义整型指针变量p,以及整型变量a */
p=&a; /* 将a的地址赋给p,即p指向a */
printf("%d", *p); /* 输出p指向的变量a的值 */
例8-2. 取地址运算和间接访问运算示例
阅读下列代码,写出运行结果
#include<stdio.h>
int main()
{
int a=3, *p;
p=&a;
printf("value of p: %x\n", p);
printf("a=%d, *p=%d\n", a, *p);
*p=10; /* 改变地址所指向的内存的内容 */
printf("a=%d, *p=%d\n", a, *p); /* &*p与&a的关系? *&a与a的关系? */
printf("Enter a:");
scanf("%d", &a);
printf("a=%d, *p=%d\n", a, *p);
(*p)++;
printf("a=%d, *p=%d\n", a, *p); /* 为什么? */
*p++; /* 等价于*(p++),实现过程如何? */
printf("value of p: %x\n", p);
printf("a=%d, *p=%d\n", a, *p);
}
int a=3;
int *p1, *p2;
p1=&a; /* 将a的地址赋给p1,p1指向a */
p2=p1; /* p2也指向a */
相同类型的指针才能相互赋值
例8-3. 角色互换
有两个角色分别用变量a和b表示,为实现角色互换,现制定了3套方案,通过函数调用交换变量a和b的值,即swap1(), swap2()和swap3(),请分析这3个函数,哪个函数可以实现这样的功能?
#include<stdio.h>
int main()
{
int a=1, b=2;
int *pa=&a, *pb=&b;
swap1(a, b);
printf("After calling swap1: a=%d, b=%d\n", a, b);
a=1, b=2;
swap2(pa, pb);
printf("After calling swap1: a=%d, b=%d\n", a, b);
a=1, b=2;
swap3(pa, pb);
printf("After calling swap1: a=%d, b=%d\n", a, b);
return 0;
}
swap1(a, b);
void swap1(int x, int y)
{
int t;
t=x;
x=y;
y=t;
}
a=1, b=2
swap2(&a, &b);
void swap2(int *px, int *py)
{
int t;
t=*px;
*px=*py;
*py=t;
}
a=2, b=1
swap3(&a, &b);
void swap3(int *px, int *py)
{
int *pt;
pt=px;
px=py;
py=pt;
}
a=1, b=2
swap1(a, b);
void swap1(int x, int y)
{
int t;
t=x;
x=y;
y=t;
}
在swap1()函数中改变了形参x,y的值,但不会反过来影响到实参的值
即swap1()不能改变main()中实参a和b的值
swap2(&a, &b);
void swap2(int *px, int *py)
{
int t;
t=*px;
*px=*py;
*py=t;
}
在swap2()函数中交换*px和*py的值,主调函数中a和b的值也相应交换了
即值传递,地址未变,但存放的变量值改变了
swap3(&a, &b);
void swap3(int *px, int *py)
{
int *pt;
pt=px;
px=py;
py=pt;
}
在swap3()中直接交换形参指针px和py的值
即值传递,形参指针的改变不会影响实参
要通过函数调用改变主调函数中某个变量的值,步骤如下:
例8-4. 输出对应年、月、日
输入年和天数,输出对应的年、月、日。要求定义和调用函数month_day(int year, int yearday, int *pmonth, int *pday),其中,year是年,yearday是天数,pmonth和pday指向的变量保存计算得到的月和日。例如,输入2000和61,输出2000-3-1,即2000年的第61天是3月1日
#include<stdio.h>
void month_day(int year, int yearday, int *pmonth, int *pday)
{
int k, leap;
int tab[2][13]={
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
leap= (year%4==0 && year%100!=0) || (year%400==0);
for(k=1; yearday>tab[leap][k]; k++)
yearday-=tab[leap][k];
*pmonth=k;
*pday=yearday;
}
int main()
{
int day, month, year, yearday;
printf("Input year and yeaday:");
scanf("%d%d", &year, &yearday);
month_day(year, yearday, &month, &day);
printf("%d-%d-%d\n", year, month, day);
return 0;
}
例8-5. 冒泡排序
输入n(n≤10)个正整数,将它们从小到大排序后输出,要求使用冒泡排序算法
#include<stdio.h>
#define MAXN 10
void swap(int *px, int *py)
{
int t;
t=*px;
*px=*py;
*py=t;
}
void bubble_sort(int a[], int n)
{
int i, j;
for(i=1; i<n; i++){
for(j=0; j<n-1; j++){
if(a[j]>a[j+1]){
swap(&a[j], &a[j+1]);
}
}
}
}
int main()
{
int n, a[MAXN];
int i;
printf("Enter n(n<=10):");
scanf("%d", &n);
printf("Enter %d integers:", n);
for(i=0; i<n; i++)
scanf("%d", &a[i]);
bubble_sort(a, n);
printf("After sort:");
for(i=0; i<n; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
数组名代表一个地址
其值是数组
int a[100];
int *p;
p=a;
int sum=0;
for(i=0; i<100; i++)
sum=sum+*(a+i);
任何用数组下标来实现的操作
int a[100];
int *p;
p=a; /* 或者p=&a[0]; */
int sum=0;
for(i=0; i<100; i++)
sum=sum+p[i];
/* 用指针完成操作 */
for(p=a; p<=&a[99]; p++)
sum=sum+*p;
&a[i] <==> a+i a[i] <==> *(a+i)
p+i <==> &p[i] *(p+i) <==> p[i]
例8-6. 数组计算
输入n(n≤10)个正整数,分别使用数组和指针计算并输出它们的和
#include<stdio.h>
#define MAXN 10
int main()
{
int i, n, a[MAXN], *p;
long sum=0;
printf("Enter n(n<=10):");
scanf("%d", &n);
printf("Enter %d integers:", n);
for(i=0; i<n; i++)
scanf("%d", &a[i]);
/* 数组计算和 */
for(i=0; i<n; i++)
sum+=*(a+i);
printf("Calculated by array, sum is %ld\n", sum);
for(p=a; p<a+n; p++)
sum+=*p;
printf("Calculated by pointer, sum is %ld\n", sum);
return 0;
}
例8-7
使用指针计算数组元素个数和数组元素的存储单元数
#include<stdio.h>
int main()
{
double a[2], *p, *q;
p=&a[0]; /* p=a */
q=p+1;
printf("%d\n", q-p); /* 指针p和q之间的元素个数 */
printf("%d\n", (int)q-(int)p); /* 指针p和q之间的字节数 */
return 0;
}
定义如前,double *p, *q; p=a, q=p+1;
double fact(int n)
{
int i;
double result=1;
for(i=1; i<=n; i++)
result*=i;
return result;
}
int main()
{
int a[5]={1, 4, 5, 7, 9};
int i, n=5;
double sum;
sum=0;
for(i=0; i<n; i++)
sum+=fact(a[i]);
printf("sum=%d\n", sum);
return 0;
}
将
int sum(int *a, int n)
{
int i, s=0;
for(i=0; i<n; i++)
s+=a[i]; /* s+=*(a+i) */
return s;
}
int main()
{
int b[5]={1, 4, 5, 7, 9};
printf("%d\n", sum(b, 5)); /* b[0]+b[1]+b[2]+b[3]+b[4] */
printf("%d\n", sum(b, 3)); /* b[0]+b[1]+b[2] */
printf("%d\n", sum(b+1, 3)); /* b[1]+b[2]+b[3] */
printf("%d\n", sum(&b[2], 3)); /* b[2]+b[3]+b[4] */
return 0;
}
int binary_search(int *p, int n, int x) /* 二分查找,在数组p中,共有n个元素中查找x */
{
int low, high, mid;
low=0, high=n-1;
while(low<=high){ /* 开始查找区间为整个数组 */
mid=(low+high)/2; /* 中间位置 */
if(x==p[mid]) /* 查找成功,中止循环 */
break;
else if(x<p[mid]) /* 前半段,high前移 */
high=mid-1;
else
low=mid+1; /* 后半段,low后移 */
}
if(low<=high)
return mid; /* 找到,返回下标 */
else
return -1; /* 未找到,返回-1 */
}
数组名作为函数参数,在函数调用时,将实参数组首元素的地址传给形参(指针变量),因此,形参也指向实参数组的首元素。如果改变形参所指向单元的值,就是改变实参数组首元素的值
形参数组和实参数组共用一段存储空间,如果形参数组中元素的值发生改变,实参数组中元素的值也同时发生改变
对n个元素的数组利用冒泡排序进行从小到大排序,而数组元素已按从大到小的顺序进行排序,总共需要进行的比较和交换次数为$n(n-1)/2$,如果元素已经按从小到大排序好,需要进行比较的次数也是$n(n-1)/2$
例8-8. 字符串压缩
输入一个长度小于80的字符串,现按规则对字符串进行压缩,输出压缩后的字符串。
压缩规则:如果某个字符x连续出现n(n≤1)个,则将这n个字符串替换为"nx"的形式,否则保持不变,如
#include<stdio.h>
#define MAXLINE 80
void zip(char *p)
{
char *q=p;
int n;
while(*p!='\0'){
n=1;
while(*(p+n)==*p)
n++;
if(n>10){
*q++=n/10+'0';
*q++=n%10+'0';
}else if(n>=2){
*q++=n+'0';
}
*q++=*(p+n-1);
p=p+n;
}
*q='\0';
}
int main()
{
char line[MAXLINE];
printf("Input the string: ");
gets(line);
zip(line);
puts(line);
return 0;
}
字符串常量,如"array", "point"是一对用双引号括起来的字符序列,可看作是一个特殊的一维字符数组,在内存中连续存放,实质上是一个指向该字符串首字符的指针常量,有
char sa[]="array";
char *sp="point";
char sa[]="array";
char *sp="point";
printf("%s\n", sa);
printf("%s\n", sa+2);
printf("%s\n", sp);
printf("%s\n", sp+3);
printf("%s\n", "string");
printf("%s\n", "string"+1);
数组名sa,指针sp和字符串"stiring"的值都是
如果要改变数组sa所代表的字符串,只能改变数组元素的内容
如果要改变指针sp所代表的字符串,通常直接改变指针的值,使其指向一个新的字符串
strcpy(sa, "Hello");
sp="Hello";
sa="Hello"; // 非法,不能对数组名赋值,
定义字符指针后,如果没有对它赋值,指针值是不确定的,因此需要先将其初始值置为
char *s;
scanf("%s", s); /* 不能引用未赋值的指针 */
char *s, str[20];
s=NULL; /* 初始置空值 */
s=str;
scanf("%s", s);
函数原型定义在头文件
函数 | 功能 | 头文件 |
---|---|---|
puts(str) | 输出字符串 | stdio.h |
gets(str) | 输入字符串(回车间隔) | |
strcpy(s1, s2) | s1 ==> s2 | string.h |
strcat(s1, s2) | s1 "+" s2 ==> s1 | |
strcmp(s1, s2) | 若s1 "==" s2,值为0 | |
若s1 ">" s2,值>0 | ||
若s1 "<" s2,值<0 | ||
strlen(str) | 计算字符串的有效长度,不包括'\0' | |
char str[80];
i=0;
while((str[i]=getchar())!='\n')
i++;
str[i]='\0';
scanf("%s", str);
gets(str);
char str[80];
for(i=0; str[i]!='\0'; i++)
putchar(str[i]);
printf("%s", str);
printf("%s", "Hello");
printf("Hello");
puts(str);
puts("Hello");
strcpy(str1, str2)将字符串
static char str1[20];
static char str2[20]="Happy";
strcpy(str1, str2);
strcpy(str1, "World");
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20], str2[20];
printf("Enter str2: ");
gets(str2);
strcpy(str1, str2);
printf("After copy: ");
puts(str1);
return 0;
}
strcat(str1, str2)连接两个字符串str1和str2,将结果放在str1中
#include<stdio.h>
#include<string.h>
int main()
{
char str1[80], str2[80];
printf("Enter str1:")
gets(str1);
printf("Enter str2:");
gets(str2);
strcat(str1, str2);
printf("After concatenation:")
puts(str1);
return 0;
}
strcmp(str1, str2)比较两个字符串的大小,比较规则按照字典序(ASCII码序)
如果str1和str2相等,返回0
如果str1大于str2,返回正整数
如果str1小于str2,返回负整数
static char s1[20]="sea";
strcmp(s1, "Sea"); /* 返回正整数 */
strcmp("Sea", "Sea "); /* 返回负整数 */
strcmp("Sea", "Sea"); /* 返回0 */
#include<stdio.h>
#include<string.h>
int main()
{
int res;
char s1[20], s2[20];
gets(s1);
gets(s2);
res=strcmp(s1, s2);
printf("%d\n", res);
return 0;
}
注意区分比较
char str1[20], str2[20];
printf("%d", str1>str2);
printf("%d", str1<"Hello");
printf("%d", str1==str2);
比较字符串首元素的地址
char str1[20], str2[20];
strcmp(str1, str2);
strcmp(str1, "Hello");
strcmp("Hello", str2)
比较字符串的内容
strlen(str)计算字符串的有效长度,不包括'\0'
static char str[20]="How are you?"
printf("strlen%d\n", strlen(str)); /* 12 */
printf("%d\n", strlen("Hello")); /* 5 */
例8-9. 求最小字符串
输入n个字符串,输出其中最小的字符串
#include<stdio.h>
#include<string.h>
int main()
{
int i, n;
char sx[80], smin[80];
scanf("%d", &n);
scanf("%s", sx);
strcpy(smin, sx);
for(i=1; i<n; i++){
scanf("%s", sx);
if(strcmp(sx, smin)<0)
strcpy(smin, sx);
}
printf("Min is %s\n", smin);
return 0;
}
例8-10. 任意个整数求和
先输入一个正整数n,再输入任意n个整数,计算并输出这n个整数的和。要求使用动态内存分配方法为这n个整数分配空间
#include<stdio.h>
#include<stdlib.h>
int main()
{
int n, sum, i, *p;
printf("Enter n:");
scanf("%d", &n);
/* 为数组p动态分配n个整数类型大小的空间 */
if((p=(int*)calloc(n, sizeof(int)))!=NULL){
printf("Not able to allocate memory.\n");
exit(1);
}
printf("Enter %d integers:", n);
for(i=0; i<n; i++)
scanf("%d", p+i); /* 输入n个数字存入p数组 */
sum=0;
for(i=0; i<n; i++)
sum+=*(p+i);
printf("The sum is %d\n", sum);
free(p); /* 释放动态分配的空间 */
return 0;
}
变量的存储,不是由编译系统分配,而是由用户在程序中通过
使用动态内存分配能够更有效地使用内存,体现在两点,(1). 使用时申请,(2). 用完就释放。同一段内存可重复使用,且有不同的用途
void *malloc(unsigned size)
在内存的动态存储区中分配一块连续空间,其长度为size
如果申请成功,返回一个
如果申请内存空间不成功,则
返回值类型为
if(
printf("Not able to allocate memory.\n");
exit(1);
}
void *calloc(unsigned n, unsigned size)
在内存的动态存储区中分配n个连续空间,每一存储空间的长度为size,并且分配后还把存储块全部初始化为0
如果申请成功,则返回一个指向被分配内存空间的起始地址的指针
若申请内存空间不成功,返回NULL
void free(void *ptr)
释放由动态存储分配函数申请到的整块内存空间,ptr为指向要释放空间的首地址
当某个动态分配的存储块不再使用时,要及时将其释放,否则该区域会一直不可用
void *realloc(void *ptr, unsigned size)
更改以前的存储分配
ptr必须是以前通过动态存储分配得到的指针
参数size为现在需要的空间大小
如果高速失败,返回NULL,同时原来ptr指向存储块的内容不变
如果调整成功,返回一片能存放大小为size的区块,并保证该块的内容与原块一致。如果size小于原块的大小,则内容为原块前size范围内的数据,如果新块更大。则原有数据存在新块的前一部分
如果分配成功,原存储块的内容可能会被改变,因此不允许再通过ptr去使用它