指针

对应教学日历:讲次 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;
}

要点

  • 以空格和句号作为单词分隔符
  • 记录每个单词的起始位置和长度
  • 最后用 maxstartmaxlen 输出最长单词

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;
}

要点

  • fgetsgets 安全(指定最大长度)
  • 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] vs s[len-1-i]

作业重点

  • 指针与数组等价a[i]*(a + i)*(p + i)p[i]
  • 数组名是指针常量不可自增;指针变量可以自增移动
  • 指针算术以所指类型大小为单位
  • 字符串常量不可修改(char *p = "Hello";),字符数组可修改
  • *p++ 先取值再移动;*++p 先移动再取值
  • 函数通过指针参数可以”返回”多个值
  • sizeof(数组名) 是整个数组大小,sizeof(指针) 是指针大小(4/8)