瑞客论坛

 找回密码
 立即注册
查看: 1880|回复: 26

C语言程序设计指针详解

  [复制链接]

金币23  第15588名

1

主题

106

回帖

588

积分

高级会员

Rank: 4

威望
250
贡献
315
热心值
0
金币
23
注册时间
2020-9-29
发表于 2023-11-8 16:06 | 显示全部楼层 |阅读模式
指针是什么
  • 如果在程序中定义了一个变量,在对程序进行编译时,系统就会给该变量分配内存单元
  • 编译系统根据程序中定义的变量类型,分配一定长度的空间
  • 例如:vc2010为整型变量分配4个字节,对单精度浮点型变量分配4个字节,对字符型变量分配1个字节
  • 内存区的每一个字节有一个编号,这就是“地址”,它相当于旅馆中的房间号
  • 在地址所标识的内存单元中存放数据,这相当于旅馆房间中居住的旅客一样
  • 由于通过地址能找到所需的变量单元,我们可以说,地址指向该变量单元
  • 将地址形象化地成为“指针”
  • 务必弄清楚存储单元的地址和存储单元的内容这两个概念的区别

直接存取和间接存取
  • 通过变量名的访问成为直接存取
  • 通过指针变量的访问成为间接存取

指向就是通过地址来体现的
  • 假设 i_pointer中的值是变量i的地址(2000),这样就在 i_pointer和变量i之间建立起一种联系,即通过i_pointer能知道i的地址,从而找到变量i的内存单元
  • 称i_pointer指向i
  • 变量i_pointer称为指针变量,它保留了变量 i 的地址
  • 由于通过地址能找到所需的变量单元,因此可以说“地址指向该变量单元”,但这样的说法不够形象和生动
  • 将地址形象化地称为“指针”。意思是通过他能找到以他为地址的内存单元
  • 可以通过指针指向某个存储单元,从而可以通过指针间接地访问这个存储单元
  • 一个变量的地址称为该变量的指针,例如:地址2000 是变量 i的指针
  • 如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为 指针变量
  • i_pointer 就是一个指针变量,指针变量就是地址变量,即专门用来存放其他变量的地址的变量,指针变量的值是地址(即指针)
  • 存放地址的变量是指针变量,它用来指向另一个对象,如变量、数组、函数和指针等
  • 指针本身就是一种类型,另外指针的类型还取决于它所指向的变量的类型(基类型)包括:普通变量指针、数组指针、函数指针、指向指针的指针

int * pointer_1   //此处 * 与类型名在一起共同定义指针变量,类型名 int 用于定义指针变量的基类型
​
*pointer //此处 * 与指针变量一起使用,此时代表指针变量所指向的变量,标识 “取内容”定义指针变量
  • 定义指针变量的一般形式位:
    类型 * 指针变量名;
    如:int *poniter_1
    • int 是为指针变量的 “基类型”
    • 基类型定义了指针变量可以指向的变量类型
    • 如 pointer_1 可以指向整型变量,但不能指向浮点型变量
    • 指针变量前面的 “ * ” 表示该变量的类型为指针型变量,指针变量名为pointer_1,而不是 *pointer_1


指针变量定义注意事项
  • 在定义指针变量时必须指定基类型
  • 不同类型的数据在内存中所占得字节数和存放方式是不同的
  • 通过指针引用一个变量时,必须知道该数据的类型,才能按存储单元的长度以及数据的存储形式正确地取出该数据
  • 指针的移动和指针的加、减运算。例如:使指针移动一个位置或者使指针值加1,这个1代表与基类型对于的字节长度
  • 整型指针变量只能指向其定义时由“基类型”int支出的形同类型的变量,不能忽而指向int整型变量,忽而指向float实型变量


在说明指针变量时不能只说“a是一个指针变量”,而应该完整的说:
a是指向整型数据的指针变量  int *a
b是指向单精度型数据的指针变量 float *b
c是指向字符型数据的指针变量  char *c
int *  ,float * ,char * 是三种不同类型的指针变量,不能混淆
指针变量中只能存放其他对象的地址(指针),不能将一个整数直接赋给一个指针变量,否则判定位非法。

下面都是合法的指针变量定义和初始化:
float *pointer_3;
char *pointer_4;
int a,b;
int *pointer_1=&a, *pointer_2=&b;怎样引用指针变量
  • 在引用指针变量时,可能有三种情况:
    • 给指针变量赋值。如: p = &a;   // 使p 指向a
    • 引用指针变量指向的变量,如有 p = &a; *p=1; // *p相当于a
    • 直接引用指针变量的值。如: printf("%o",p);


掌握两个与指针相关的运算符:
1、& 取地址运算符。    &a 是变量a的地址
2、* 指针运算符(间接访问运算符)
如果:p指向变量 a , 则 *p 就代表 a
k = *p;  (把a的值赋值给k)
*p = 1; (把1赋值给a)

例子: 输入 a 和 b两个整数,按先大后小的顺序输出a和b
  • C语言实参变量和形参变量之间的数据传递是单向的“值传递”方式
  • 用指针变量作为函数参数时同样要遵循这一规则,此时传递的是地址值


//例题:输入3个整数,a,b,c,要求按由大到小的顺序将他们输出,用函数实现。
#include<stdio.h>
int main(){
    void exchange(int *q1, int *q2, int *q3);
    int a,b.c,*p1,*p2,*p3;
    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;
}易错点
#include<stdio.h>
#include<conio.h>
int main(){
    float *a;
    float   b = 3;
    a=&b;
    printf("%f,%f\n",*a++,*a);
    *a  = 5.0;
    printf("%f\n",*a);
    printf("%f\n",b);
    getch();
    return 0;
}数组元素的指针
  • 一个变量有地址,一个数组包含若干元素,每个数组元素都有相应的地址
  • 指针变量可以指向数组元素(把某一数组元素的地址放到一个指针变量中)
  • 所谓数组元素的指针就是数组元素的地址
  • 引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。
  • 使用指针法能使目标程序质量高(占内存少,运行速度快)
  • C 语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序号为0的元素)的地址。它是一个指针型常量
    可以用一个指针变量指向一个数组元素
    int a[6] = {1,2,3,4,5,6};
    int *p;
    p = &a[0]  //等价于 p = a ;

在引用数组元素时指针的运算在指针指向数组元素时,允许以下运算:
  • 加一个整数(用+或+=),如 p+1
  • 减一个整数(用-或-=),如 p-1
  • 自加运算,如 p++, ++p ,但是如果a 是数组名,则 a++  a-- 不合法
  • 自减运算,如 p--, --p
  • 两个指针相减,如 p1 - p2 (只有p1 和 p2都指向同一个数组时才有意义)
    1、如果指针变量p已指向数组中的一个元素,则p+1指向同一个数组中的下一个元素,p-1指向同一个数组中的上一个元素
    例子: float  a[10],*p = a;
    假设a[0]的地址为2000 则
    &#8203;        p的值为 2000
    &#8203;    p+1的值为2004
    &#8203;    p-1的值为1996,但这个位置不能用(越界)
    2、如果p的初值为&a[0],则p+i和a+i就是数组元素a的地址,或者说,他们指向a数组序号为i的元素
    3、* (p+i) 或  * (a+i) 是p+i 或 a+i所指向的数组元素,即 a
    4、如果指针 p1 和 p2 都指向同一个数组 则 p2 - p1得到的是他们之间相差多少个元素

引用一个数组元素,可以用下面两种方法:
  • 下标法,如a形式
  • 指针法,如 * (a+i) 或者 *(p+i),其中a是数组名,p是指向数组a中数组元素的指针变量,其初值 p=a


例题:通过指针变量输出整型数组a的10个元素
解题思路:用指针变量p指向数组元素,通过改变指针变量的值,使p先后指向a[0]到a[9]各元素。
#include<stdio.h>
int main(){
    int *p,i,a[10];
    p=a;
    for(i=0;i<10;i++) scanf("%d",p++);
    p=a;//一定不能掉了,这种错误在编译时不会报错,一定要注意
    for(i=0;i<10;i++,p++)
    printf("%d",*p);
}
注意事项:
  • 指向数组的指针变量也可以带下标,如p在编译时,对下标的处理方法时转换为地址,对p处理成*(p+i)
  • 如果p是指向一个整型数组元素a[0],则p代表a。但如果当前p指向a[3],则 p[2]并不代表a[2],而是a[3+2],即a[5]

利用指针引用数组元素比较方便灵活,有不少技巧,
  • 设p指向数组a的首元素即p=a,则:

p++;
*p
p++使p指向下一元素a[1],然后若再执行*p,则得到下一个元素a[1]的值
*p++;
由于++ 和 *同优先级,结合方向为自右向左,它等价于 *(p++)。先引用p的值,实现 *p的运算,然后再使p自增,等同 *p; p++;
*(p++) 与 *(++p)作用是否相同?
  • 不相同,前者是先取*p的值,然后使p加1
  • 后者是先使p加1指向下一个数组元素,然后再取*p的内容

  • 如果p当前指向 a 数组中的第i个元素a 则:
    *(p--) 相当于 a ,先对p进行 * 运算(求p所指向元素的值)。再使p 自减
    *(++p)相当于 a[++i],先使p自加,再进行 * 运算
    *(--p)相当于 a[--i],先使p自减,再进行 * 运算


//例题:想输出a数组的100个元素,可以用下面的方法:
用数组名做函数参数
  • 用数组名作函数参数时,因为实参数组名代表该数组首元素的地址,形参应该是一个相应类型的指针变量
  • C编译都是将形参数组名作为指针变量来处理,因此可以省略数组第一维的大小
  • 实参数组名是指针常量,而形参数组名是按指针变量处理
  • 在函数调用进行虚实结合后,形参数组名(指针变量)的值就是实参数组首元素的地址
  • 在函数执行期间,形参数组名(指针变量)可以再被赋值(因为它本质上是一个变量)

变量名作函数参数和数组名做函数参数的比较
实参类型
变量名
数组名

要求形参的类型
变量名
数组名或指针变量

传递的信息
变量的值
实参数组首元素的地址

通过函数调用能否改变实参的值
不能改变实参变量的值
能改变实参数组的值

//将数组a中n个整数按相反顺序存放
//阶梯思路:将a[0] 与 a[n-1] 对换
#include<stdio.h>
#include<conio.h>
int main(){
    void inv(int *x,int n);
    int i, a[10] = {3,7,9,11,0,6,7,5,4,2};
    for (i = 0; i < 10; i++) printf("%d",a);
    printf("\n");
    inv(a,10);
    for ( i = 0; i < 10; i++) printf("%d",a);
    getch();
    return 0;
}
void inv(int *x,int n){
    int *p,temp,*i,*j,m=(n-1) / 2;
    i = x;
    j=x + n - 1;
    p =x + m;
    for (; i <= p; i++,j--)
    {
        temp = *i;
        *i = *j;
        *j = temp;
    }
   
}
如果有一个实参数组,要想在函数中改变此数组中元素的值,函数实参与形参的对于关系
实参
形参
实参
形参

数组名数组名指针变量指针变量
数组名指针变量指针变量数组名

//用指针方法对10个整数按由大到小顺序排列
/*
    解题思路:
        在主函数中定义数组a存放10个整数,定义 int * 型指针变量p指向 a[10]
        定义函数 sort 使数组a中的元素按由大到小的顺序排列
        在主函数中调用 sort 函数 用指针 p 做实参
        用选择法进行排序
        什么是选择法排序?
          选择法排序:假设有N个数要按照从大到小的顺序排序,选择法就是先设第一个数是最大的(进行第一次大循环),然后将这个数与数组中剩下的数依次比较,如果剩下的数中有比这个数大的,那就两者交换,直至第一个数是最大的为止;然后再设第二个数为第二大的(进行第二次大循环),将第二个数与数组中除第1、2数外的其余数进行比较,如果有大值,则进行两两交换,直至第二个数是剩下数中最大的为止。
&#8203;
*/
&#8203;
#include<stdio.h>
#include<conio.h>
int main(){
    void sort(int x[], int n);
    int i, *p,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");
    getch();
    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])  // if(*(x+j) > *(x + k))
            {
                k = j;
            }
        }
        if(k != i) {
            t = x;  // t = *(x+i)
            x = x[k];  // *(x+i) = *(x+k)
            x[k] = t;  // *(x+k) = t
        }   
    }
}通过指针引用多维数组
  • 多维数组元素的地址

a 和 *(a+i)等价,a+j和  *(a+i)+ j等价
*(行指针) = 列指针
从二维数组角度看,a代表二维数组首元素的地址,现在的首元素不是一个简单的整型元素,而是由4个整型元素所组成的一维数组,因此a代表的是二维数组首行(即第0行)的首地址,a+1代表第一行的首地址,a+2代表第二行的首地址。
a[0],a[1],a[2],既然是一位数组名,而c语言又规定了数组名代表数组首元素地址,因此a[0]代表一维数组a[0]中第0列元素的地址,即&a0。也就是说 a[1]的值是&a1,a[2]的值是&a2
  • a 代表第0行首地址
  • a + 1 代表第 1 行首地址
  • a + 2 代表第 2 行首地址  (行指针每 加 1 ,走一行)
  • a + i 代表行号为 i  的行首地址(按行变化)

提问 * (a+i)代表什么?
答:相当于 a
  • a[0] 代表 a0的地址
  • a[0] + 1 代表a0的地址
  • a[0] + 2 代表 a0的地址 (列指针每加1 ,走一列)

提问 a + j代表什么?
答:代表ai的地址
提问 *(a + j)代表什么?
答:代表元素ai
提问 *( *(a+i) + j)代表什么?
答:代表元素ai
a[1]和 *(a+1)等价
a+1 和 *(a+1)、a[1]都是地址,且其值相等,都等于&a1,区别在于
  • a + 1是行指针
  • *(a+1)、a[1] 都是列指针

在指向行的指针前面加一个 * ,就转换为指向列的指针。例如,a 和 a + 1是指向行的指针,在它们前面加一个 * 后, * a 和 *(a+1)就成为指向列的指针,分别指向a数组第0行0列的元素和 第 1行0列的元素。
反之,在指向列的指针前面加 & 就成为指向行的指针。例如 a[0]的指向第0行0列元素的列指针,在它前面加 一个 & ,得 &a[0],由于a[0]与*(a+0)等价,因此&a[0]与 & * a等价,也就是与 a 等价,它指向二维数组的第 0 行,是一个行指针。
不要把&a简单地理解为元素a的物理地址,因为并不存在a这样一个实际的数据存储单元。它只是一种地址的计算方法,能得到第 i 行的首地址(行指针方式)。
&a和a的值相同,但他们的含义不同
&a或 a+ i指向行(行指针)   a或* (a+i)指向列(列指针)   
它们所指向的对象不同,即指针的基类型是不同的。
*(a+i)只是a的另一种表示形式,不要简单地认为 *(a+i) 是 a+i 所指单元中的内容(实际上 a代表一个一维数组,它不是一个实际的存储单元)。
在一维数组中a+i所指的是一个数组元素的存储单元,在该单元中有具体值,上述说法是正确的,而对二维数组,a + i 不是指向具体存储单元而是指向数组a的第 i 行
在二维数组中,a + i 、&a、a、*(a+i)、&ai的值相等,即他们都代表同一地址,其中:
a + i 、&a 是行指针
a、*(a+i)、&ai是列指针
例题:显示二维数组的有关数据(地址和值)

//例题1:有一个3 x 4的二维数组,要求用指向数组元素的指针变量输出二维数组各元素的值
/*
    解题思路
    列指针完成
    1.二维数组的元素时整型的,它相当于整型变量,可以用int * 型指针变量指向它
    2.二维数组的元素在内存中是按行顺序存放的,即存放完序号为0的行中的全部元素后,接着存放序号为1的行中的元素,依次类推
    3。因此可用一个指向整型元素的指针变量(列指针),依次指向二维数组中的各个数组元素
*/
#include <stdio.h>
#include <conio.h>
int main(){
    int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
    int *p;//p + 1
    for (p=*a; p < a[0] + 12; p++)
    {
        if((p - a[0]) % 4 == 0) printf("\n");
        printf("%4d", *p);
    }
    printf("\n");
    getch();
    return 0;
}
&#8203;
//行指针完成
#include <stdio.h>
#include <conio.h>
int main(){
    int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
    int (*p)[4];//p + 1
    int j;
    for (p=a; p < a + 3; p++){
        for(j=0;j < 4;j++)
        {
            printf("%4d", *(*p+j));
        }
        printf("\n");
    }
    getch();
    return 0;   
}


回复

使用道具 举报

金币19  第16445名

51

主题

3175

回帖

1万

积分

论坛元老

Rank: 8Rank: 8

威望
6315
贡献
5772
热心值
2
金币
19
注册时间
2019-12-6

最佳新人活跃会员灌水之王一年荣誉奖章三年荣誉奖章在线达人

发表于 2023-11-8 19:05 | 显示全部楼层
看到这帖子真是高兴!
回复

使用道具 举报

金币1227  第1415名

0

主题

1899

回帖

7804

积分

论坛元老

Rank: 8Rank: 8

威望
4068
贡献
2509
热心值
0
金币
1227
注册时间
2022-8-3
发表于 2023-11-8 19:16 | 显示全部楼层
看到这帖子真是高兴!
回复

使用道具 举报

金币2227  第784名

0

主题

1092

回帖

1万

积分

论坛元老

Rank: 8Rank: 8

威望
4381
贡献
5089
热心值
0
金币
2227
注册时间
2020-3-25
发表于 2023-11-8 19:22 | 显示全部楼层
强烈支持楼主ing……
回复

使用道具 举报

金币1504  第1166名

0

主题

795

回帖

5131

积分

论坛元老

Rank: 8Rank: 8

威望
2133
贡献
1494
热心值
0
金币
1504
注册时间
2022-4-10
发表于 2023-11-8 19:31 | 显示全部楼层
强烈支持楼主ing……
回复

使用道具 举报

金币786  第2128名

0

主题

2436

回帖

1万

积分

论坛元老

Rank: 8Rank: 8

威望
6580
贡献
5040
热心值
0
金币
786
注册时间
2021-3-21
发表于 2023-11-8 22:37 | 显示全部楼层
强烈支持楼主ing……
回复

使用道具 举报

金币232  第5220名

0

主题

663

回帖

3373

积分

论坛元老

Rank: 8Rank: 8

威望
1805
贡献
1336
热心值
0
金币
232
注册时间
2023-10-27
发表于 2023-11-9 00:09 | 显示全部楼层
Don't stay mad for too long. Learn to leave things behind.
回复

使用道具 举报

金币1430  第1230名

0

主题

1840

回帖

8908

积分

论坛元老

Rank: 8Rank: 8

威望
4689
贡献
2789
热心值
0
金币
1430
注册时间
2023-1-18
发表于 2023-11-9 00:10 | 显示全部楼层
强烈支持楼主ing……
回复

使用道具 举报

金币987  第1736名

0

主题

949

回帖

5651

积分

论坛元老

Rank: 8Rank: 8

威望
2667
贡献
1997
热心值
0
金币
987
注册时间
2023-1-28
发表于 2023-11-9 00:24 | 显示全部楼层
看到这帖子真是高兴!
回复

使用道具 举报

金币1337  第1302名

0

主题

915

回帖

8075

积分

论坛元老

Rank: 8Rank: 8

威望
3388
贡献
3350
热心值
0
金币
1337
注册时间
2022-3-26
发表于 2023-11-9 06:58 来自手机 | 显示全部楼层
激动人心的时刻到了吗
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|瑞客论坛 |网站地图

GMT+8, 2024-12-23 15:10

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表