指针
对应教学日历:讲次 20-22(6月4日-6月11日)
一、指针基础回顾
| 概念 | 说明 |
|---|---|
int *p; | 定义指针变量 p,可存放 int 类型变量的地址 |
p = &x; | 取 x 的地址赋给 p(p 指向 x) |
*p | 解引用:访问 p 所指向变量的值 |
& | 取地址运算符 |
* | 定义时声明指针;使用时解引用 |
int x = 10;
int *p = &x; // p 存 x 的地址
*p = 20; // 修改 p 指向的内容,x 变为 20二、指针的算术运算
指针 ± 整数:以所指类型大小为单位移动。
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // p 指向 a[0]
p++; // p 指向 a[1](地址增加了 sizeof(int)=4 字节)| 运算 | 效果 |
|---|---|
p + n | 往后移 n 个元素 |
p - n | 往前移 n 个元素 |
p++ | 后移一个元素 |
p - q | 两指针间的元素个数(同数组内) |
p > q | 比较指针位置 |
不能对指针进行乘除运算,不能将两个指针相加。
三、指针与一维数组
数组名
a就是指向首元素的指针常量(不可修改自身值)。
等价关系(核心!)
int a[5];
int *p = a;
// 以下写法两两等价:
a[i] ≡ *(a + i) ≡ p[i] ≡ *(p + i)
&a[i] ≡ a + i ≡ &p[i] ≡ p + i理解:
*(a + i)就是a[i],因为a是首地址,a + i是第 i 个元素的地址,*取出该地址的值。
遍历数组的三种写法
// 下标法
for (i = 0; i < n; i++)
printf("%d ", a[i]);
// 指针法(偏移)
for (i = 0; i < n; i++)
printf("%d ", *(a + i));
// 指针法(移动)
for (p = a; p < a + n; p++)
printf("%d ", *p);数组名 vs 指针变量
数组名 a | 指针变量 p | |
|---|---|---|
| 能否修改自身值 | ❌ 不能 a++ | ✅ 可以 p++ |
| sizeof | 整个数组的字节数 | 指针本身的字节数(4/8) |
| &a | 数组的地址(类型为数组指针) | 指针变量的地址 |
四、指针与字符串
C 语言中字符串常量返回的是首字符的地址。
char *p = "Hello"; // p 指向字符串常量的首字符
char str[] = "Hello"; // str 是字符数组,内容可修改
// p[0] = 'h'; // 错误!字符串常量不可修改
// str[0] = 'h'; // 正确,字符数组可以修改指针操作字符串常用模式
// 遍历字符串
char *p;
for (p = str; *p != '\0'; p++)
printf("%c", *p);
// 拷贝字符串
void strcpy_ptr(char *dst, char *src)
{
while (*src != '\0')
*dst++ = *src++;
*dst = '\0';
}
// 或更简洁:
void strcpy_ptr(char *dst, char *src)
{
while (*dst++ = *src++); // 同时复制和判断 \0
}快速记忆
*p++:先取*p的值,p 再往后移*++p:p 先后移,再取*p的值(*p)++:取*p的值,该值自增
五、指针与函数
指针作为参数
void swap(int *a, int *b) // 传递地址
{
int t = *a;
*a = *b;
*b = t;
}
// 调用:swap(&x, &y);数组作为参数(等价于指针)
void func(int arr[]) { ... } // 等价于
void func(int *arr) { ... }返回指针的函数
char *strcat(char *dst, const char *src)
{
char *p = dst;
while (*p) p++;
while (*p++ = *src++);
return dst;
}函数指针(了解)
int (*pf)(int, int); // pf 是指向函数的指针
pf = max; // pf 指向 max 函数
int result = pf(3, 5); // 通过指针调用函数六、指针数组(了解)
char *weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
// weekdays[0] 指向 "Sun" 的首字符
// weekdays 是一个数组,元素类型是 char*作业示例
ex11-1:指针遍历——大写转换
#include<stdio.h>
int main()
{
char s[100],*p;
int i=0,imax=0;
gets(s);
while(s[i]!='\0')
{
if(s[i]>s[imax]) imax=i; // 找到最大字符的位置
i++;
}
p=&s[imax]; // p 指向最大字符
while(*p!='\0')
{
if('a'<=*p && *p<='z')
*p=*p-32; // 小写转大写
p++;
}
puts(s);
return 0;
}要点:
- 先用下标找到最大字符位置,再用指针操作后半段
p++移动指针遍历字符串*p解引用读写字符
ex11-2:指针过滤——提取小写字母
#include<stdio.h>
#include<string.h>
int main()
{
int i,j,k;
char a[80],b[80];
gets(a);
k=strlen(a);
for(j=0,i=0;i<k;i++)
if(a[i]>='a'&&a[i]<='z')
b[j++]=a[i]; // 只有小写字母复制到 b
b[j]='\0';
puts(b);
return 0;
}要点:
- 虽然此题用下标法,但
j作为独立写入下标体现了双指针思想 - 过滤后记得加
\0
ex11-3-1:指针实现字符串倒序
#include<stdio.h>
#include<string.h>
int main()
{
char a[80];
char *p,*q;
gets(a);
printf("倒序后的字符串为:");
for(p=a,q=&a[strlen(a)-1]; p<q; p++,q--)
{
char t=*p;
*p=*q;
*q=t;
}
puts(a);
return 0;
}要点:
p指向头,q指向尾p++, q--→ 两指针向中间靠拢- 交换
*p和*q
ex11-4:指针找最长单词
#include<stdio.h>
int main()
{
char a[500];
int i=0,start=0,len=0,maxlen=0,maxstart=0;
gets(a);
while(a[i]!='\0')
{
if(a[i]==' '||a[i]=='.')
{
if(len>maxlen)
{
maxlen=len;
maxstart=start;
}
start=i+1; // 下一个单词的开始
len=0;
}
else
len++;
i++;
}
for(i=0;i<maxlen;i++)
printf("%c",a[maxstart+i]);
printf("\n");
return 0;
}要点:
- 以空格和句号作为单词分隔符
- 记录每个单词的起始位置和长度
- 最后用
maxstart和maxlen输出最长单词
ex11-5:指针数组——字符串排序
#include<stdio.h>
#include<string.h>
int main()
{
char a[5][80],t[80];
int i,j;
for(i=0;i<5;i++)
gets(a[i]);
for(i=0;i<4;i++)
for(j=0;j<4-i;j++) // 注意:代码原文有 bug
if(strcmp(a[j],a[j+1])>0)
{
strcpy(t,a[j]);
strcpy(a[j],a[j+1]);
strcpy(a[j+1],t);
}
for(i=0;i<5;i++)
puts(a[i]);
return 0;
}要点:
- 二维字符数组存储多个字符串
strcmp比较字符串大小strcpy交换字符串内容- 用冒泡排序对字符串排序
注意:原文内循环
j<4-i写法应为j<4-i(冒泡正确写法),但<<显然误写。
ex14-1:指针参数——通过指针返回多个值
#include <stdio.h>
void sum_diff(float op1, float op2, float *psum, float *pdiff)
{
*psum = op1 + op2;
*pdiff = op1 - op2;
}
int main(void)
{
float a, b;
float sum, diff;
scanf("%f %f", &a, &b);
sum_diff(a, b, &sum, &diff); // 传地址
printf("%.2f %.2f\n", sum, diff);
return 0;
}要点:
- 函数通过指针参数”返回”两个值(C 语言只能 return 一个值)
&sum、&diff传地址给指针参数- 函数内
*psum = ...写入实参变量
ex14-2:综合——字符串删除字符(fgets版)
#include <stdio.h>
#include <string.h>
void delete_character(char str[], char ch)
{
int i, j;
for (i = 0, j = 0; str[i] != '\0'; i++) {
if (str[i] != ch) {
str[j++] = str[i];
}
}
str[j] = '\0';
}
int main(void)
{
char str[1000];
char ch;
fgets(str, sizeof(str), stdin); // 安全输入
int len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
str[len - 1] = '\0'; // 去掉末尾的 \n
}
ch = getchar();
delete_character(str, ch);
printf("%s\n", str);
return 0;
}要点:
fgets比gets安全(指定最大长度)fgets会保留换行符\n,需手动去除- 删除字符本质是覆盖(快慢指针)
ex14-3:指针实现 strmcpy(从第 m 个字符开始复制)
#include <stdio.h>
#include <string.h>
void strmcpy(char *t, int m, char *s)
{
int len = strlen(t);
if (m > len) {
s[0] = '\0'; // 空字符串
} else {
strcpy(s, t + m - 1); // t+m-1 指向第 m 个字符
}
}
int main()
{
char t[1000], s[1000];
int m;
fgets(t, sizeof(t), stdin);
t[strcspn(t, "\n")] = '\0'; // 去掉换行
scanf("%d", &m);
strmcpy(t, m, s);
printf("%s\n", s);
return 0;
}要点:
t + m - 1→ 地址偏移(第 m 个字符 = 下标 m-1)s[0] = '\0'→ 产生空字符串strcspn找到换行位置
ex14-4:回文判断
#include <stdio.h>
#include <string.h>
int palindrome(char *s) {
int i, len;
len = strlen(s);
for (i = 0; i < len / 2; i++) {
if (s[i] != s[len - 1 - i]) {
return 0; // 不是回文
}
}
return 1; // 是回文
}
int main() {
char s[100];
scanf("%s", s);
if (palindrome(s)) {
printf("%s\n", s);
} else {
printf("NO\n");
}
return 0;
}要点:
- 回文:正读反读一样的字符串
- 只需比较前一半和后一半是否对称
s[i]vss[len-1-i]
作业重点
- 指针与数组等价:
a[i]≡*(a + i)≡*(p + i)≡p[i] - 数组名是指针常量不可自增;指针变量可以自增移动
- 指针算术以所指类型大小为单位
- 字符串常量不可修改(
char *p = "Hello";),字符数组可修改 *p++先取值再移动;*++p先移动再取值- 函数通过指针参数可以”返回”多个值
sizeof(数组名)是整个数组大小,sizeof(指针)是指针大小(4/8)