《C语言程序设计 谭浩强 第4版》:善于使用指针

一、什么是指针

一个变量的地址称为该变量的 “ 指针 ” 

如果有一个变量用来存放另一个变量的地址,称为 “ 指针变量 ” 

指针变量就是地址变量 

指针变量的值是地址 

内存区的每一个字节有一个编号,这就是内存单元的 “ 地址 ” 

通过地址能找到所需的变量单元,地址指向该变量单元 

在 C 语言中,将地址形象化地称为 “ 指针 ” 

 

按 C 语言的规定,可以定义一种特殊的变量,用于 存放地址 : point = &a ,表示变量 point 存放变量 a 的内存地址 

 

直接按变量名进行访问称为 “ 直接访问 ” 

将变量 a 的地址存放在另一个变量中,称为 “ 间接访问 ” 

所谓指向就是通过地址来体现的 

 

二、指针变量 

1、使用指针变量访问变量的例子 

通过指针变量访问整型变量 

#include 

int main(){
    int a,b;
    int *pointer_1,
    *pointer_2;
    a = 100;
    b = 10;
    pointer_1 = &a;
    pointer_2 = &b;
    printf("a = %d,b = %d\n",a,b);
    printf("*pointer_1 = %d,*pointer_2 = %d\n",*pointer_1,*pointer_2);
    return 0;
}

运行结果:
a = 100,b = 10
*pointer_1 = 100,*pointer_2 = 10 

* 表示所定义的变量是指针变量,不是普通变量 

 

2、怎样定义指针变量 

基类型 *指针变量名
如:
int *pointer_1;

左端的 int 是在定义指针变量时必须指定的 “ 基类型 ” 

指针变量的基类型用来指定指针变量可以指向的变量的类型 

*pointer_1 变量名是 pointer_1 ,而不是 *pointer_1 

赋给指针变量的是变量地址而不能是任意类型的数据,而且只能是与指针变量的基类型相同类型的变量的地址 

指针变量中只能存放地址,不要将一个整型值赋给一个指针变量 

 

3、怎样引用指针变量 

*p 是指针变量,p 指向的对象的值 

  • 给指针变量赋值 
p = &a;
// 指针变量 p 的值是变量 a 的地址
  • 引用指针变量指向的变量 
printf("%d",*p);
// 以十进制整数形式输出指针变量 p 所指向的变量的值,即变量 a 的值


*p = 1;
// 将整数 1 赋给 p 当前所指向的变量
// 如果 p 指向变量 a,相当于 a = 1
  • 引用指针变量的值 
printf("%o",p);
// 以八进制数形式输出指针变量 p 的值
// 如果 p 指向了 a,就是输出了 a 的地址,即 &a

 

输入 a 和 b 两个整数,按先大后小的顺序输出 a 和 b 

#include 

int main(){
    int *p1,*p2,*p,a,b;
    scanf("%d,%d",&a,&b);
    p1 = &a;
    p2 = &b;
    if(a < b){
        p = p1;
        p1 = p2;
        p2 = p;
    }
    printf("a = %d,b = %d\n",a,b);
    printf("max = %d,min = %d\n",*p1,*p2);
    return 0;
}

运行结果:
5,9
a = 5,b = 9
max = 9,min = 5 

指针代表的不是一个纯地址,而是一个带类型的地址 

在 C 语言中,所有数据都是存放在内存单元中,因此所有数据都是有类型的 

 

4、指针变量作为函数参数

指针变量作为函数的参数时,其作用是将一个变量的地址传送到另一个函数中 

 

输入 a 和 b 两个整数,按先大后小的顺序输出 a 和 b 

#include 

int main(){
    void swap(int *p1,int *p2);
    int a,b;
    int *pointer_1,
    *pointer_2;
    scanf("%d,%d",&a,&b);
    pointer_1 = &a;
    pointer_2 = &b;
    if(a < b){
        swap(pointer_1,pointer_2);
    }
    printf("max = %d,min = %d",a,b);
    return 0;
}

// 交换的是 a 和 b 的值,而不是 p1 和 p2 的值
void swap(){
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

《C语言程序设计 谭浩强 第4版》:善于使用指针_第1张图片

如果要通过函数调用得到 n 个要改变的值,可以这样做:

  1. 在主调函数中设 n 个变量,用 n 个指针变量指向它们
  2. 设计一个函数,有 n 个指针形参。在这个函数中进行一系列操作改变 n 个形参的值
  3. 在主调函数中调用这个函数,在调用时将这 n 个指针变量作实参,将它们的地址传给该函数的形参
  4. 在执行该函数的过程中,通过形参指针变量,改变它们所指向的 n 个变量的值
  5. 主调函数中就可以使用这些改变了值的变量

不可能通执行函数改变实参指针变量的值,但是可以改变实参指针所指变量的值 

函数的调用可以仅且可以得到一个返回值,而使用指针变量作参数,可以得到多个变化了的值 

 

输入 3 个整数 a,b,c,要求按大小顺序将它们输出。用函数实现改变这 3 个变量的值 

#include 

int main(){
    void exchange(int *q1,int *q2,int *q3);
    int a,b,c,*p1,*p2,*p3;
    printf("Pleases enter three numbers : ");
    scanf("%d,%d,%d",&a,&b,&c);
    p1 = &a;
    p2 = &b;
    p3 = &c;
    exchange(p1,p2,p3);
    printf("%d,%d,%d\n",a,b,c);
    return 0;
}

void exchange(int *q1,int *q2,int *q3){
    void swap(int *pt1,int *pt2);
    if(*q1 < *q2){
        swap(q1,q2);
    }
    if(*q1 < *q3){
        swap(q1,q3);
    }
    if(*q2 < *q3){
        swap(q2,q3);
    }
}

void swap(int *pt1,int *pt2){
    int temp;
    temp = *pt1;
    *pt1 = *pt2;
    *pt2 = temp;
}

 

三、通过指针引用数组 

1、数组元素的指针 

一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址 

数组元素的指针就是数组元素的地址 

int a[10]; // 定义 a 为包含 10 个整型数据的数组
int *p; // 定义 p 为指向整型变量的指针变量
p = a[0]; // 把 a[0] 元素的地址赋给指针变量 p,也就是 p 指向数组下标为 0 的元素


// 以下两个语句等价
// 数组名 a 不代表整个数组中的全部数据,p = a 是把 a 数组第一个元素的地址赋给指针变量 p,而不是所有元素
p = a[0];
p = a;


int *p;
p = &a[0]; // 注意不是 *p = &a[0]
// 等价于
int *p = a;

 

2、通过指针引用数组元素 

  • 下标法: a[i]
  • 指针法,即地址法 

数组名代表数组首元素的地址,故可以通过数组名计算出数组中序号为 i 的元素的地址,其形式为 *(a + i) 

用一个指针变量 p 指向数组首元素,然后用 *(p + i) 调用 a 数组中序号为 i 的元素 

 

有一个数组存放 10 个学生的年龄,用不同的方法输出数组中的全部元素 

#include 

int main(){
    int a[10] = {19,17,20,18,16,22,24,15,23,25};
    int i,*p = a;

    // 方式一:用数组名加下标
    for(i = 0;i < 10;i++){
        printf("%d",a[i]);
    }
    printf("\n");

    // 方式二:通过数组名计算数组元素地址,找到元素
    for(i = 0;i < 10;i++){
        printf("%d",*(a + i));
    }
    printf("\n");

    // 通过指针变量计算数组元素地址,找到元素
    for(i = 0;i < 10;i++){
        printf("%d",*(p + i));
    }
    printf("\n");

    // 用指针变量先后指向各数组元素
    for(p;p < (a + 10);p++){
        printf("%d",*p);
    }

    printf("\n");
    return 0;
}

 

指针运算 

  • 如果指针变量 p 已指向数组中的一个元素,则 p + 1 指向同一数组中的下一个元素
    • p + 1 不是将 p 的值(地址)简单的加 1,而是加一个数组元素所占用的字节数
  • 如果指针变量 p 的初值为 &a[0],则 p + i 和 a + i 就是数组元素 a[i] 的地址,或者说是 a 数组的第 i 个元素
  • *(p+ i) 或 *(a + i) 是 p + i 或 a + i 所指向的数组元素,即 a[i] 
  • 如果 p 原来指向 a[0],执行 ++p 后 p 的值改变了,在 p 的原值基础上加 d,这样 p 就指向数组的下一个元素 a[i]
  • 如果指针变量 p1 和 p2 都指向同一数组,若执行 p2 - p1,结果是两个地址之差除以数组元素的长度,即两个元素之间距离的元素的个数(两个地址相加没有意义)
  • p[i] 会被处理成 *(p + i)
    • 如果 p 是指向一个整型数组元素 a[0],则 p[i] 代表 a[i]
    • 如果当前 p 指向 a[3],则 p[2] 代表 a[3 + 2],即 a[5]

 

3、用数组名做函数参数 

实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的 

因此形参应该是一个指针变量(只有指针变量才能存放地址) 

C 编译都是将形参数组名作为指针变量处理的 

实参数组名代表一个固定的地址 ,或者说是指针常量,但形参数组并不是一个固定的地址值,而是作为指针变量,在函数调用开始后,它的值等于实参数组首元素的地址,在函数执行期间,它可以再被赋值,但它的值的改变不会传递回主调函数,不会改变实参的值 

 

将数组 a 中 n 个整数按相反顺序存放 

#include 

int main(){
    void inv(int x[],int n);
    int i,a[10] = {3,7,9,11,0,6,7,5,4,2};
    printf("The origninal array : \n");
    for(i = 0; i < 10; i++){
        printf("%d ",a[i]);
    }
    printf("\n");
    inv(a,10);
    printf("The array has been inverted : \n");
    for(i = 0; i < 10; i++){
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

void inv(int x[],int n){
    int temp,i,j,m = (n - 1) / 2;
    for(i = 0; i < = m; i++){
        j = n - 1 -i;
        temp = x[i];
        x[i] = x[j];
        x[j] = temp;
    }
    return;
}

如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下 4 种情况: 

  • 形参和实参都用数组名
  • 实参用数组名,形参用指针变量 
  • 实参形参都用指针变量
    • 先使实参指针变量 p 指向数组 a ,然后将 p 作实参,将 &a[0] 传给形参指针变量 x
  • 实参为指针变量,形参为数组名
    • 必须先使实参指针变量有确定值,即指向数组的一个元素

 

编写一个函数,用选择法对 10 个整数按由大到小顺序排序,用数组名作实参 

#include 

int main(){
    void sort(int x[],int n);
    int *p,i,a[10];
    p = a;
    for(i = 0;i < 10;i++){
        scanf("%d",p++);
    }
    p = a;
    sort(p,10);
    for(p = a,i = 0;i < 10;i++){
        printf("%d",*p);
        p++;
    }
    printf("\n");
    return 0;
}

void sort(int x[],int n){
    int i,j,k,t;
    for(i = 0;i < n - 1;i++){
        k = i;
        for(j = i + 1;j < n;j++){
            if(x[j] > x[k]){
                k = j;
            }
        }
        if(k != i){
            t = x[i];
            x[i] = x[k];
            x[k] = t;
        }
    }
}

 

四、通过指针引用字符串 

1、字符串的表示形式 

在 C 语言中,可以用两种方法访问一个字符串 

  1. 用字符数组存放一个字符串,然后用字符数组名和下标访问字符数组中的元素,也可以通过字符数组名用 %s 格式符输出一个字符串 
  2. 用字符指针指向一个字符串。可以不定义字符数组,而定义一个字符指针,用字符指针指向字符串中的字符 

 

定义字符指针,指向一个字符串 

#include 

int main(){
    char *string = "I love China!";
    printf("%s\n",string);
    return 0;
}

注意:

  • C 语言对字符串常量是按字符数组处理的,在内存中开辟了一个字符数组用来存放该字符串常量,但是这个数组没有名字,不能通过数组名来引用,只能通过指针变量引用
  • 可以用指针变量指向字符串常量,但是不能通过指针变量对该字符串常量重新赋值,因为字符串常量是不能改变的!!!
  • 通过字符数组名或字符指针变量可以输出一个字符串。而对于一个数值型数组,是不能用数组名输出它的全部元素的
  • 对于字符串中的字符的存取,可以用下标法,或指针法 

 

有一个字符数组 a,在其中存放字符串 “I am a boy.”,要求把该字符串复制到字符数组 b 中 

#include 

int main(){
    char a[] = "I am a boy.",b[20];
    int i;
    for(i = 0;*(a + i) != "\0";i++){
        *(b + i) = *(a + i);
    }
    *(b + i) = '\0';
    printf("string a is : %s\n",a);
    printf("string b is : ");
    for(i = 0;b[i] != '\0';i++){
        printf("%c",b[i]);
    }
    printf("\n");
    return 0;
}

使用指针变量 

#include 

int main(){
    char a[] = "I am a boy.",*p1,*p2;
    int i;
    p1 = a;
    p2 = b;
    for(;*p1 != '\0';P1++,P2++){
        *p2 = *p1;
    }
    p2 = '0';
    printf("string a is : %s\n");
    printf("string b is : ");
    for(i = 0;b[i] != '\0';i++){
        printf("%c",b[i]);
    }
    printf("\n");
    return 0;
}

 

2、字符指针作函数参数 

有一个字符数组 a,要求把该字符串复制到字符数组 b 中,使用函数实现

#include 

int main(){
    void copy_string(char *from,char *to);
    char *a = "I am a teacher.";
    char b[] = "You are a student.";
    char *p = b;
    printf("string a = %s\nstring b = %s\n",a,p);
    printf("\ncopy string a to string b : \n");
    copy_string(a,p);
    printf("string a = %s\nstring b = %s\n",a,p);
    return 0;
}

void copy_string(char *from,char *to){
    for(;*from != '\0';from++,to++){
        *to = *from;
    }
    *to = '\0';
}

 

把字符串 a 拼接到字符串 b 后面 

#include 

int main(){
    void link_string(char *arr1,char *arr2);
    char a[40] = "I am a teacher.";
    char b[] = "You are a student.";
    char *p1 = a,*p2 = b;
    printf("string a : %s\nstring b : %s\n",p1,p2);
    link_string(p1,p2);
    printf("Now,\nstring a : %s\nstring b : %s\n",a,b);
    return 0;
}

void link_string(char *arr1,char *arr2){
    int i;
    for(i = 0;*arr1 != '\0';i++){
        arr1++; // 指针变量指向 ‘\0’
    }
    for(;*arr2 != '\0';arr1++,arr2++){
    *arr1 = *arr2;
    }
    arr1 = '\0';
}

 

3、使用字符指针变量和字符数组的区别 

字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址),绝不是讲字符串放到字符指针变量中 

赋值方式 

  • 对字符数组 a 只能对各个元素赋值,不能用以下办法对字符数组赋值 
char atr[14];
atr = "I Love China!";
  • 而对字符指针变量,可以采用下面方法赋值 
char *a;
a = "I Love China!";

注意,赋给 a 的不是字符,而是字符串第一个元素的地址 

赋初值 

  • 对字符指针变量赋初值 
char *a = "I Love China!";

等价于 

char *a;
a = "I Love China!";

而对数组的初始化 

char str[14] = {"I Love China!"};

不能等价于 

char atr[14];
str[] = "I Love China!";

即数组可以在定义时整体赋初值,但不能在赋值语句中整体赋值 

 

如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的一段地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个字符变量的地址,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据 

指针变量的值是可以改变的

若指针变量 p 指向数组 a,则可以用指针变量带下标的形式引用数组元素;若字符指针变量 p 指向字符串,就可以用指针变量带下标形式引用所指的字符串中的字符 

字符数组中各元素的值是可以改变的,但字符指针变量指向的字符串常量中的内容是不可以被取代的 

 

改变指针变量的值 

#include 

int main(){
    // 正确用法
    char *a = "I Love China!";
    a = a + 7;
    printf("%s\n",a);
    // 错误示范
    // 数组名虽然代表地址,但它是常量,值时不能改变的
    char atr[] = {"I Love China!"};
    str = atr + 7;
    printf("%s",str);
    return 0;
}

运行结果
China! 

 

八、提高部分 

1、指针使用的技巧 

上文的 copy_string 函数改造 

原代码 

void copy_string(char *from,char *to){
    for(;*from != '\0';from++,to++){
        *to = *from;
    }
    *to = '\0';
}

改造一 

void copy_string(char *from,char *to){
    while((*to = *from) != '\0'){
        to++;
        from++;
    }
}

改造二 

void copy_string(char *from,char *to){
    while((*to++ = *from++) != '\0');
}

改造三 

void copy_string(char *from,char *to){
    while(*from != '\0'){
        *to++ = *from++;
    }
    *to = '\0';
}

改造四 

void copy_string(char *from,char *to){
    while(*from){
        *to++ = *from++;
    }
    *to = '\0';
}

改造五 

void copy_string(char *from,char *to){
    while(*to++ = *from++);
    // 等价于
    // while((*to++ = *from++) != '\0');
}

改造六 

void copy_string(char *from,char *to){
    for(;(*to++ = *from++) != 0;);
    // 或
    for(;*to+= = *from++;);
}

改造七 

void copy_string(char from[],char to[]){
    char *p1,*p2;
    p1 = from;
    p2 = to;
    while((*p2++ = *p1++) != '\0');
}

 

2、指向函数的指针

如果在程序中定义了一个函数,在编译时,编译系统给这个函数代码分配一段存储空间,这段存储空间的起始地址称为这个 函数的指针 

可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,此时此指针变量指向该函数 

数据类型 (*指针变量名)(函数参数列表);

 

3、返回指针值的函数 

类型名 *函数名(参数列表);

注意:在 * 号两侧没有括号,有括号就成了指向函数的指针变量了 

 

4、指针数组

一个数组若其元素均为指针类型数据,称为 指针数组 ,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量 

类型名 数组名[数组长度]

 

5、多重指针 -- 指向指针的指针 

指向另一个指针数据的指针变量,称为 指向指针的指针 

char **p;

指针变量可以有 “空值”,即该指针变量不指向任何变量 

p = NULL;

其中 NULL 是一个符号常量,代表整数 0 

在 stdio.h 头文件中对 NULL 进行了定义 

#define NULL 0

它使 p 指向地址为 0 的单元 

系统保证使该单元不作他用(不存放有效数据),即有效数据的指针不指向 0 单元 

注意: 

p 的值为 NULL 与未对 p 赋值时两种不同的概念。前者是有值的(值为 0),不指向任何程序变量,后者虽未对 p 赋值但也不等于 p 无值,只是它的值是一个无法预料的值,也就是 p 可能指向一个事先未指定的单元 

 

指针变量的定义形式 

《C语言程序设计 谭浩强 第4版》:善于使用指针_第2张图片

 

 一  叶  知  秋,奥  妙  玄  心

你可能感兴趣的:(C语言,c语言)