精华内容
下载资源
问答
  • 2021-05-21 07:32:25

    第6章 指针

    1. 指针

    指针简介:所有的数据都是存放在存储器中。一般把存储器中的一个字节称为一个内存单元,为了正确的访问这些内存单元,必须在每个内存单元编上号,根据一个内存单元的编号即可准确的找到该内存单元;内存单元的编号也可叫为地址;即根据内存单元的编号或地址就可以找到所需的内存单元,通常也把这个地址称为指针

    对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内存,用一个变量来存放指针,这种变量称为指针变量;一个指针变量的值就是某个内存单元的地址或称为某个内存单元的指针

    “指针”是指地址,是常量;“指针变量”是指取值为地址的变量;定义指针的目的是为了通过指针去访问内存单元

    2. 指针变量的类型说明

    对指针变量的类型说明:

    1) 指针类型说明,即定义变量为一个指针变量

    2) 指针变量名

    3) 变量值(指针)所指向的变量的数据类型

    其一般形式: 类型说明符 *变量名

    其中 ***** 表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型

    3. 指针变量的赋值

    指针变量的赋值:只能赋予地址,决不能赋予任何其他数据;提供了地址运算符 & 来表示变量的地址,一般形式: & 变量名; 如&a表示变量a的地址,&b表示变量b的地址。 变量本身必须预先说明。设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下两种方式:

    (1)指针变量初始化的方法

    1 int a;

    2 int *p=&a;

    (2)赋值语句的方法

    1 int a;

    2 int *p;

    3 p=&a;

    不允许把一个数赋予指针变量,故下面的赋值是错误的: int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符,如写为 *p=&a; 也是错误的

    4. 指针变量的运算

    指针变量是可以进行某些运算,只能进行赋值运算 和 部分算术运算及关系运算

    指针运算符

    1) 取地址运算符 &: 是单目运算符,其结合性是自右至左,其功能是取变量的地址

    2) 取内容运算符 * : 是单目运算符,其结合性是自右至左,用来表示指针变量所值的变量

    赋值运算:

    1) 指针变量初始化赋值

    2) 把一个变量的地址赋予指向相同数据类型的指针变量

    1 int a, *pa;

    2 pa = &a;

    3) 把一个指针变量的值赋予指向相同类型变量的另一个指针变量

    1 int a, *pa = &a, *pb;

    2 pb = pa;

    4) 把数组的首地址赋予指向数组的指针变量

    1 // 数组名表示数组的首地址,故可赋予指向数组的指针变量pa

    2 int a[6], *pa; pa = a;

    3 // 数组第一个元素的地址也是整个数组的首地址

    4 pa = &a[0];

    5) 把字符传的首地址赋予指向字符类型的指针变量

    1 // 初始化赋值的方式

    2 // 并不是把整个字符串装入指针变量,而是把存放该

    3 // 字符串的字符数组的首地址装入指针变量

    4 char *pc = "C Language"

    6) 把函数的入口地址赋予指向函数的指针变量

    1 // f为函数名

    2 int (*pf)(); pf = f;

    加减算术运算:对于指向数组的指针变量,可以加上或减去一个整数n,指针变量加或减一个整数n的意义:是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置

    1) 因为数组有不同的类型,各种类型的数组元素所占的字节长度不同;如指针变量加 1,即向后移动 1 个位置表示指针变量指向下一个数组元素的首地址,而不是原地址基础上加 1

    1 int a[5], *pa;

    2 pa = a; // pa指向数组a,也是指向a[0]

    3 pa = pa + 2; // pa指向a[2],即pa的值为&pa[2]

    2) 指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的

    3) 两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算

    4) 两指针变量相减:所得之差是两个指针所指数组元素之间相差的元素个数;

    5) 两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示他们所指数组元素之间的关系:

    pf1 == pf2表示pf1和pf2指向同一数组元素

    pf1 > pf2表示pf1处于高地址位置

    pf1 < pf2表示pf2处于低地址位置

    设 p 为指针变量,则 p == 0 表明 p 是空指针,不指向任何变量; p != 0; 表示不是空指针;#define NULL 0 int *p = NULL; 对指针变量赋0值和不赋值是不一样的,为赋值时,可以是任意值,但不能使用,将造成意外错误;赋0值后,可以使用,只是不指向具体的变量而已

    5. 数组指针变量的说明和使用

    数组指针:指向数组的指针变量。

    一个数组是由连续的一块内存单元组成的,数组名是这连续内存单元的首地址,一个数组也是由各个数组元素(下标变量) 组成的,每个数组元素按其类型不同占有几个连续的内存单元,一个数组元素的首地址也是指它所占有的几个内存单元的首地址。一个指针变量即可以指向一个数组,也可以指向一个数组元素,可把数组名或第一个元素的地址赋予它;如要使指针变量指向第 i 号元素可以把 i 元素的首地址赋予它或把数组名加 i 赋予它

    数组指针变量说明:一般形式: 类型说明符 指针变量名其中类型说明符表示所指数组的类型

    引入指针变量后,可以用以下方式访问数组元素:

    · 下标发:即用 a[i]形式访问数组元素

    · 指针法:即采用(pa + i)形式,间接访问的方法来访问数组元素

    6. 指向多维数组的指针变量

    多维数组的指针变量:是一种地址计算方法,表示数组 a 第 i 行首地址。因此我们得出:a[i], &a[i], *(a + i), 和 (a + i) 都是相同的。

    a[0]也可以看成 a[0]+0是一维数组 a[0]的0号元素的首地址,而 a[0]+1 则是 a[0] 的1号元素首地址,由此得出 a[i]+j 则是一维数组 a[i] 的j号元素首地址,他等于 &a[i][j]; 由 a[i] = *(a+i) 得 a[i]+j = *(a+i)+j 由于 *(a+i)+j 是二维数组 a 的 i 行 j 列元素的首地址; 该元素的值等于 *(*(a+i)+j)

    二维数组的指针变量:其一般形式:类型说明符 (*指针变量名)[长度]; 其中”类型说明符“为所指数组的数据类型;“*” 表示其后的变量是指针类型;“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也是二维数组的列数;

    7. 使用字符串指针变量与字符数组的区别

    都可以实现字符串的存储 和 运算,两者区别的说明:

    1) 字符串指针变量本身是一个变量,用于存放字符串的首地址;而字符串本身是存放在以该首地址为首的一快连续的内存空间中并以"\0"作为串的结束。字符数组是由于若干个数组元素组成的,可用来存放整个字符串

    2) 对字符数组做初始化赋值,必须采用外部类型或静态类型,如:static char st[] = {"C Language"};; 而对字符串指针变量则无次限制;如:char *ps = "C Language"

    3) 对字符串指针方式:char *ps = "C Language" 可以写为:char *ps; ps = "C Language"; 而对数组方式不能写成:char st[20]; st = {"C Language"};只能对字符数组的元素逐个赋值

    8. 函数指针变量

    函数指针变量:一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。可以把函数的这个首地址(入口地址)赋予一个指针变量,使该指针变量指向该函数,然后通过指针变量就可以找到并调用这个函数。

    函数指针变量定义的一般形式: 类型说明符 (指针变量名)(), ""表示后面的变量是定义的指针变量;最后的空括号表示指针变量所指的是一个函数

    更多相关内容
  • 二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,...C语言中的二维数组是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a
  • 深入理解C语言指针

    万次阅读 多人点赞 2019-09-28 08:36:51
    一、指针的概念 要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。 1.1、变量和地址 先写一段...

    一、指针的概念

    要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。

    1.1、变量和地址

    先写一段简单的代码:

    void main(){
    	int x = 10, int y = 20;
    }
    

    这段代码非常简单,就是两个变量的声明,分别赋值了 10、20。我们把内存当做一个酒店,而每个房间就是一块内存。那么“int x = 10;”和“int y = 20;”的实际含义如下:

    1. 去酒店订了两个房间,门牌号暂时用 px、py 表示
    2. 让 10 住进 px,让 20 住进 py
    3. 其中门牌号就是 px、py 就是变量的地址
    4. x 和 y 在这里可以理解为具体的房间,房间 x 的门牌号(地址)是 px,房间 y 的门牌号(地址)是 py。而 10 和 20,通过 px、py 两个门牌,找到房间,住进 x、y。用户(变量的值)和房间(变量)以及房间号(指针、地址)的关系

    1.2、指针变量和指针的类型

    指针变量就是一个变量,它存储的内容是一个指针。如果用前面的例子,可以理解为指针变量就是一张房卡,房卡存储了房间号的信息。

    在我们定义一个变量的时候,要确定它的类型。int x、char ch、float、、、在定义指针变量时也是一样的,必须确定指针类型。int 变量的指针需要用 int 类型的指针存储,float 变量的指针需要用 float 类型的指针存储。就像你只能用酒店 A 的房卡存储酒店 A 中房间号的信息一样。

    二、变量的指针与指针变量

    变量的指针就是变量的存储地址,指针变量就是存储指针的变量。

    2.1、指针变量的定义及使用

    (1)指针变量的定义

    指针变量的定义形式如:数据类型 *指针名;例如:

    //分别定义了 int、float、char 类型的指针变量
    int *x;
    float *f;
    char *ch;
    

    如上面的定义,指针变量名为 x、f、ch。并不是*x、*f、*ch

    (2)指针变量的使用

    • 取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。
    • 指针运算符*(间接寻址符):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。

    代码示例:

    //声明了一个普通变量 a
    int a;
    //声明一个指针变量,指向变量 a 的地址
    int *pa;
    //通过取地址符&,获取 a 的地址,赋值给指针变量
    pa = &a;
    //通过间接寻址符,获取指针指向的内容
    printf("%d", *pa);
    

    (3)“&”和“*”的结合方向

    “&”和“*”都是右结合的。假设有变量 x = 10,则*&x 的含义是,先获取变量 x 的地址,再获取地址中的内容。因为“&”和“*”互为逆运算,所以 x = *&x。

    接下来做个小练习,输入 x、y 两个整数,然后将其中的值大的赋值给 x,小的赋值给 y。即:假设输入 x = 8,y = 9。就将 9 赋值给 x,8 赋值给 y。

    void main(){
    	//声明两个普通变量
    	int x, y;
    	//声明两个指针变量
    	int *px, *py;
    	//声明一个临时变量,用于交换
    	int t;
    	//输入两个值,赋值给 x、y
    	scanf("%d", &x);
    	scanf("%d", &y);
    	//给指针变量 px、py 赋初值(关联变量 x、y)
    	px = &x;
    	py = &y;
    	//利用指针来对比 x、y 的值,如果 x 的值比 y 的值小,就交换
    	if(*px < *py){
    		//交换步骤,其中*px == x、*py == y
    		t = *px;
    		*px = *py;
    		*py = t;
    	}
    	printf("x =  %d, y = %d", *px, *py);
    }
    
    输入:23 45
    输出结果为:x = 45, y = 23
    

    2.2、指针变量的初始化

    指针变量与其它变量一样,在定义时可以赋值,即初始化。也可以赋值“NULL”或“0”,如果赋值“0”,此时的“0”含义并不是数字“0”,而是 NULL 的字符码值。

    //利用取地址获取 x 的地址,在指针变量 px 定义时,赋值给 px
    int x;
    int *px = &x;
    //定义指针变量,分别赋值“NULL”和“0”
    int *p1= NULL, *p2 = 0;
    

    2.3、指针运算

    (1)赋值运算

    指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址

    int *px, *py, *pz, x = 10;
    //赋予某个变量的地址
    px = &x;
    //相互赋值
    py = px;
    //赋值具体的地址
    pz = 4000;
    

    (2)指针与整数的加减运算

    1. 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
    2. 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
    //定义三个变量,假设它们地址为连续的,分别为 4000、4004、4008
    int x, y, z;
    
    //定义一个指针,指向 x
    int *px = &x;
    
    //利用指针变量 px 加减整数,分别输出 x、y、z
    printf("x = %d", *px);		//因为 px 指向 x,所以*px = x
    
    //px + 1,表示,向前移动一个单元(从 4000 到 4004)
    //这里要先(px + 1),再*(px + 1)获取内容,因为单目运算符“*”优先级高于双目运算符“+”
    printf("y = %d", *(px + 1));		
    printf("z = %d", *(px + 2));
    

    (3)关系运算

    假设有指针变量 px、py。

    1. px > py 表示 px 指向的存储地址是否大于 py 指向的地址
    2. px == py 表示 px 和 py 是否指向同一个存储单元
    3. px == 0 和 px != 0 表示 px 是否为空指针
    //定义一个数组,数组中相邻元素地址间隔一个单元
    int num[2] = {1, 3};
    
    //将数组中第一个元素地址和第二个元素的地址赋值给 px、py
    int *px = &num[0], *py = &num[1];
    int *pz = &num[0];
    int *pn;
    
    //则 py > px
    if(py > px){
    	printf("py 指向的存储地址大于 px 所指向的存储地址");
    }
    
    //pz 和 px 都指向 num[0]
    if(pz == px){
    	printf("px 和 pz 指向同一个地址");
    }
    
    //pn 没有初始化
    if(pn == NULL || pn == 0){
    	printf("pn 是一个空指针");
    }
    

    三、指针与数组

    之前我们可以通过下标访问数组元素,学习了指针之后,我们可以通过指针访问数组的元素。在数组中,数组名即为该数组的首地址,结合上面指针和整数的加减,我们就可以实现指针访问数组元素。

    3.1、指向数组的指针

    如以下语句:

    int nums[10], *p;
    

    上面语句定义了一个数组 nums,在定义时分配了 10 个连续的int 内存空间。而一个数组的首地址即为数组名nums,或者第一个元素的首地址也是数组的首地址。那么有两种方式让指针变量 p 指向数组 nums:

    //数组名即为数组的首地址
    p = nums;
    //数组第一个元素的地址也是数组的首地址
    p = &nums[0];
    

    上面两句是等价的。
    如下几个操作,用指针操作数组:

    1. *p = 1,此操作为赋值操作,即将指针指向的存储空间赋值为 1。此时 p 指向数组 nums 的第一个元素,则此操作将 nums 第一个元素赋值为 0,即 nums[0] = 1。
    2. p + 1,此操作为指针加整数操作,即向前移动一个单元。此时 p + 1 指向 nums[0]的下一个元素,即 nums[1]。通过p + 整数可以移动到想要操作的元素(此整数可以为负数)。
    3. 如上面,p(p + 0)指向 nums[0]、p + 1 指向 nums[1]、、、类推可得,p+i 指向 nums[i],由此可以准确操作指定位置的元素。
    4. 在 p + 整数的操作要考虑边界的问题,如一个数组长度为 2,p+3 的意义对于数组操作来说没有意义。

    下面写一段代码,用指针访问数组的元素:

    //定义一个整形数组,并初始化
    int nums[5] = {4, 5, 3, 2, 7};
    
    //定义一个指针变量 p,将数组 nums 的首地址赋值给 p,也可以用p = &nums[0]赋值
    int *p = nums, i;			//i 作为循环变量
    
    //p 指向数组第一个元素(数组首地址),我们可以直接用间接寻址符,获取第一个元素的内容
    printf("nums[0] = %d\n", *p);			//输出结果为 nums[0] = 4
    
    //我们可以通过“p + 整数”来移动指针,要先移动地址,所以 p + 1 要扩起来
    printf("nums[1] = %d\n", *(p + 1));		//输出结果为 nums[1] = 5
    
    //由上面推导出*(p + i) = nums[i],所以我们可以通过 for 循环变量元素
    for(i = 0; i < 5; i++){
    	printf("nums[%d] = %d", i, *(p + i));
    }
    

    注:数组名不等价于指针变量,指针变量可以进行 p++和&操作,而这些操作对于数组名是非法的。数组名在编译时是确定的,在程序运行期间算一个常量。

    3.2、字符指针与字符数组

    在 C 语言中本身没有提供字符串数据类型,但是可以通过字符数组和字符指针的方式存储字符串。

    (1)字符数组方式

    这个在前面应该学习过,这里就不赘述了。

    char word[] = "zack";
    printf("%s", word);
    

    (2)字符指针方式

    指针方式操作字符串和数组操作字符串类似,可以把定义的指针看做是字符数组的数组名。在内存中存储大致如下,这里为了方便换了个字符串:在这里插入图片描述

    //除了定义一个字符数组外,还可以直接定义一个字符指针存储字符串
    char *sentence = "Do not go gentle into that good night!";
    
    //此时可以做字符串的操作
    //输出
    printf("%s", sentence);
    
    //通过下标取字符
    printf("%c", sentence[0]);
    
    //获取字符串长度,其中 strlen 是 string.h 库中的方法
    printf("%d", strlen(sentence));
    

    注:字符指针方式区别于字符数组方式,字符数组不能通过数组名自增操作,但是字符指针是指针,可以自增操作。自增自减少会实现什么效果大家可以自己尝试运行一下

    下面做个小练习,利用字符指针将字符数组 sentence 中的内容复制到字符数组 word 中:

    //定义字符数组 sentence 和 word,给 sentence 赋初值
    char sentence[] = "Do not go gentle into that good night!", word[100];
    
    //定义字符指针,指向 word
    char *ch = word;
    int i;
    
    //循环赋值
    for(i = 0; sentence[i] != '\0'; i++){
    	*(ch + i) = sentence[i];
    }
    
    //在当 i 等于 sentence 的长度(sentence 的长度不包含'\0')时,
    //i 继续自增,此时判断 sentence[0] != '\0'不符合,跳出循环,则 i 比 sentence 长度大 1
    *(ch + i) = '\0';
    
    //输出字符串,因为 ch 指向 word,所以输出结果是一样的
    printf("ch = %s, word = %s", ch, word);
    

    注:指针变量必须初始化一个有效值才能使用

    3.3、多级指针及指针数组

    (1)多级指针

    指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。我们先看看二级指针,它们关系如下:指针变量 p 指向变量 x,二级指针变量指向指针变量 p
    其中 p 为一级指针,pp 为二级指针。二级指针定义形式如下:

    数据类型 **二级指针名;
    

    和指针变量的定义类似,由于*是右结合的,所以*pp 相当于*(*p)。在本次定义中,二级指针的变量名为 pp,而不是**p。多级指针的定义就是定义时使用多个“*”号。下面用一个小程序给大家举例:

    //定义普通变量和指针变量
    int *pi, i = 10;
    //定义二级指针变量
    int **ppi;
    
    //给指针变量赋初值
    pi = &i;
    
    //给二级指针变量赋初值
    ppi = &pi;
    
    //我们可以直接用二级指针做普通指针的操作
    //获取 i 的内容
    printf("i = %d", **ppi);
    //获取 i 的地址
    printf("i 的地址为%d", *ppi);
    

    注:在初始化二级指针 ppi 时,不能直接 ppi = &&i,因为&i 获取的是一个具体的数值,而具体数字是没有指针的。

    (2)指针数组

    指针变量和普通变量一样,也能组成数组,指针数组的具体定义如下:

    数据类型 *数组名[指针数组长度];
    

    下面举一个简单的例子熟悉指针数组:

    //定义一个数组
    int nums[5] = {2, 3, 4, 5, 2}, i;
    
    //定义一个指针数组
    int *p[5];
    
    //定义一个二级指针
    int **pp;
    
    //循环给指针数组赋值
    for(i = 0; i < 5; i++){
    	p[i] = &nums[i];
    }
    
    //将指针数组的首地址赋值给 pp,数组 p 的数组名作为 p 的首地址,也作为 p 中第一个元素的地址。
    //数组存放的内容为普通变量,则数组名为变量的指针;数组存放的内容为指针,则数组名为指针的指针。
    pp = p;
    
    //利用二级指针 pp 输出数组元素
    for(i = 0; i < 5; i++){
    	//pp == &p[0] == &&nums[0],nums[0] == *p[0] == **pp
    	printf("%d", **pp);
    	
    	//指针变量+整数的操作,即移动指针至下一个单元
    	pp++;
    }
    

    3.4、指针与多维数组

    讲多维数组是个麻烦的事,因为多维数组和二维数组没有本质的区别,但是复杂度倒是高了许多。这里我主要还是用二维数组来举例,但是还是会给大家分析多维数组和指针的关系。

    (1)多维数组的地址

    先用一个简单的数组来举例:

    int nums[2][2] = {
    	{1, 2},
    	{2, 3}
    };
    

    我们可以从两个维度来分析:

    1. 先是第一个维度,将数组当成一种数据类型 x,那么二维数组就可以当成一个元素为 x 的一维数组。
    2. 如上面的例子,将数组看成数据类型 x,那么 nums 就有两个元素。nums[0]和 nums[1]。
    3. 我们取 nums[0]分析。将 nums[0]看做一个整体,作为一个名称可以用 x1 替换。则 x1[0]就是 nums[0][0],其值为 1。
      在这里插入图片描述

    我们知道数组名即为数组首地址,上面的二维数组有两个维度。首先我们把按照上面 1 来理解,那么 nums 就是一个数组,则nums 就作为这个数组的首地址。第二个维度还是取 nums[0],我们把 nums[0]作为一个名称,其中有两个元素。我们可以尝试以下语句:

    printf("%d", nums[0]);
    

    此语句的输出结果为一个指针,在实验过后,发现就是 nums[0][0]的地址。即数组第一个元素的地址。

    如果再多一个维度,我们可以把二维数组看做一种数据类型 y,而三维数组就是一个变量为 y 的一维数组。而数组的地址我们要先确定是在哪个维度,再将数组某些维度看成一个整体,作为名称,此名称就是该维度的地址(这里有些绕)。

    例:

    //假设已初始化,二维数组数据类型设为 x,一维数组数据类型设为 y
    int nums[2][2][2];
    
    //此数组首地址为该数组名称
    printf("此数组首地址为%d", nums);
    
    //此数组可以看做存储了两个 x 类型元素的一维数组,则 nums[0] = x1 的地址为
    printf("第二个维度的首地址为%d", nums[0]);
    
    //而 x1 可以看做存储了两个 y 类型元素的一维数组,则 y1 = x1[0] = nums[0][0]
    printf("第三个维度的首地址为%d", nums[0][0]);
    
    

    三维数组实际存储形式如下:
    在这里插入图片描述
    实际存储内容的为最内层维度,且为连续的。对于 a 来说,其个跨度为 4 个单元;对 a[0]来说,其跨度为 2 个单元;对 a[0][0]来说,跨度为一个单元。有上面还可以得出:

    a == a[0] == a[0][0] == &a[0][0][0];
    

    上面的等式只是数值上相等,性质不同。

    (2)多维数组的指针

    在学习指针与数组的时候,我们可以如下表示一个数组:

    int nums[5] = {2, 4, 5, 6, 7};
    int *p = nums;
    

    在前面讲指针数组时,所有指针数组元素都指向一个数字,那么我们现在可以尝试用指针数组的每个元素指向一个数组:

    //定义一个二维数组
    int nums[2][2] = {
    	{1, 2},
    	{2, 3}
    };
    
    //此时 nums[0]、和 nums[1]各为一个数组
    int *p[2] = {nums[0], nums[1]};
    
    //我们可以用指针数组 p 操作一个二维数组
    
    //p 为数组 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0]
    printf("nums[0][0] = %d", **p);
    
    //指针 + 整数形式,p+1 移动到 nums 的地址,*(p +1) = nums[1],则**(p + 1) = nums[1][0]
    printf("nums[1][0] = %d", **(p + 1));
    
    //先*p = nums[0],再*p + 1 = &nums[0][1],最后获取内容*(*p + 1)即为 nums[0][1]
    printf("nums[0][1] = %d", *(*p + 1));
    

    这里可能不能理解为什么*p + 1 = &nums[0][1],而不是 nums[1]。*p 获得的是一个一维数组,而 int 数组 + 1 的跨度只有 4 个字节,也就是一个单元。前面 p 是一维数组的指针,其跨度为一个数组。所以*p + 1 = &nums[0][1],而 p + 1 = nums[1]。

    四、指针与函数

    前面学习函数学到,函数参数可以为 int、char、float 等,但是在操作时,这些参数只作为形参,所有操作都只在函数体内有效(除对指针的操作外),那么今天来学习一下指针作为函数参数。

    4.1、函数参数为指针

    我们直接做一个练习,定义一个函数,用来交换两个变量的内容。

    void swap(int *x, int *y);
    void main(){
    	int x = 20, y = 10;
    	swap(&x, &y);
    	printf("x = %d, y = %d", x ,y);
    }
    void swap(int *x, int *y){
    	int t;
    	t = *x;
    	*x = *y;
    	*y = t;
    }
    

    代码非常简单,我也就不细讲了。这里传入的参数为指针,所以调用 swap 方法后 x,y 的内容发生了交换。如果直接传入 x,y,那么交换只在 swap 中有效,在 main 中并没有交换。

    4.2、函数的返回值为指针

    返回值为指针的函数声明如下:

    数据类型 *函数名(参数列表){
    	函数体
    }
    //例如:
    int s;
    int *sum(int x, int y){
    	s = x + y;
    	return &s;
    }
    

    在函数调用前要声明需要对函数声明(有点编译器不需要)

    int s;
    void mian(){
    	int *r = sum(10, 9);
    	printf("10 + 9 + %d", *r);
    }
    int *sum(int x, int y){
    	s = x + y;
    	return &s;
    }
    

    除了上面的操作,更实用的是返回一个指向数组的指针,这样就实现了返回值为数组。

    4.3、指向函数的指针

    C 语言中,函数不能嵌套定义,也不能将函数作为参数传递。但是函数有个特性,即函数名为该函数的入口地址。我们可以定义一个指针指向该地址,将指针作为参数传递。

    函数指针定义如下:

    数据类型 (*函数指针名)();
    

    函数指针在进行“*”操作时,可以理解为执行该函数。函数指针不同与数据指针,不能进行+整数操作。

    下面举个例子,来使用函数指针:

    #include <string.h>
    /**
    *	定义一个方法,传入两个字符串和一个函数指针 p,用 p 对两个字符串进行操作
    */
    void check(char *x, char *y, int (*p)());
    void main(){
    	//string.h 库中的函数,使用之前需要声明该函数。字符串比较函数
    	int strcmp();
    	char x[] = "Zack";
    	char y[] = "Rudy";
    	
    	//定义一个函数指针
    	int (*p)() = strcmp;
    
    	check(x, y, p);
    }
    void check(char *x, char *y, int (*p)()){
    	if(!(*p)(x, y)){
    		printf("相等");
    	}else{
    		printf("不相等");
    	}
    }
    

    利用函数指针调用方法具体操作如下:

    (*p)(x, y);
    

    指针除了这些地方,还在结构体中用处巨大。今天就先讲到这里~·

    展开全文
  • C语言指针应用简单实例 这次来说交换函数的实现: 1、 #include #include void swap(int x, int y) { int temp; temp = x; x = y; y = temp; } int main() { int a = 10, b = 20; printf(交换前:\n a = %d...
  • 深入理解C语言指针的奥秘[参考].pdf
  • c语言指针编程练习题.pdf
  • c语言 指针

    2019-02-13 13:15:43
    学习c语言指针。ppt
  • 本文主要讲了c语言指针变量作为函数参数传递,下面一起来学习一下
  • 本篇文章是对C语言指针赋值的问题进行了详细的分析介绍,需要的朋友参考下
  • 本文给出两个C语言指针实现字符串的反转程序,感兴趣的朋友可以参考下。
  • C语言 指针

    2017-10-10 10:04:46
    C语言指针教学,新手必看,强烈推荐学习,很基础的内容!
  • C语言 指针与数组的详解及对比 通俗理解数组指针指针数组 数组指针: eg:int( *arr)[10]; 数组指针通俗理解就是这个数组作为指针,指向某一个变量。 指针数组: eg:int*arr[10]; 指针数组简言之就是存放指针的数组...
  • C语言指针详解(经典,非常详细)

    万次阅读 多人点赞 2019-06-01 17:26:12
    要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,...

    前言:复杂类型说明

        要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧:

    1. int p; //这是一个普通的整型变量 
    2. int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针 
    3. int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组 
    4. int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组 
    5. int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针 

     

    1. int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针. 
    2. int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据 
    3. Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针 
    4. int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.

    说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了.

    一、细说指针

    指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。

    先声明几个指针放着做例子:
     

    例一:

    1. (1)int*ptr; 
    2. (2)char*ptr; 
    3. (3)int**ptr; 
    4. (4)int(*ptr)[3]; 
    5. (5)int*(*ptr)[4];


     

    1.指针的类型

    从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
    (1)int*ptr;//指针的类型是int*
    (2)char*ptr;//指针的类型是char*
    (3)int**ptr;//指针的类型是int**
    (4)int(*ptr)[3];//指针的类型是int(*)[3]
    (5)int*(*ptr)[4];//指针的类型是int*(*)[4]
    怎么样?找出指针的类型的方法是不是很简单?
     

    2.指针所指向的类型

    当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
    从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
    (1)int*ptr; //指针所指向的类型是int
    (2)char*ptr; //指针所指向的的类型是char
    (3)int**ptr; //指针所指向的的类型是int*
    (4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
    (5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]

    在指针的算术运算中,指针所指向的类型有很大的作用。
    指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
     

    3.指针的值----或者叫指针所指向的内存区或地址

    指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
    以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
     

    4 指针本身所占据的内存区

    指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。

     


     

    二、指针的算术运算

    指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。例如:
     

    例二:

    1.     char a[20]; 
    2.     int *ptr=(int *)a; //强制类型转换并不会改变a 的类型 
    3.     ptr++; 


     

    在上例中,指针ptr 的类型是int*,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:

    例三:

    1.     int array[20]={0}; 
    2.     int *ptr=array
    3.     for(i=0;i<20;i++) 
    4.     { 
    5.         (*ptr)++; 
    6.         ptr++ 
    7.     } 


     

    这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下一个单元。

     

    再看例子:
     

    例四:

    1. char a[20]="You_are_a_girl"
    2. int *ptr=(int *)a; 
    3. ptr+=5;


     

    在这个例子中,ptr 被加上了5,编译器是这样处理的:将指针ptr 的值加上5 乘sizeof(int),在32 位程序中就是加上了5 乘4=20。由于地址的单位是字节,故现在的ptr 所指向的地址比起加5 后的ptr 所指向的地址来说,向高地址方向移动了20 个字节。
    在这个例子中,没加5 前的ptr 指向数组a 的第0 号单元开始的四个字节,加5 后,ptr 已经指向了数组a 的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。如果上例中,ptr 是被减去5,那么处理过程大同小异,只不过ptr 的值是被减去5 乘sizeof(int),新的ptr 指向的地址将比原来的ptr 所指向的地址向低地址方向移动了20 个字节。
    下面请允许我再举一个例子:(一个误区)

    例五:

    1.     #include<stdio.h> 
    2.     int main() 
    3.     { 
    4.         char a[20]=" You_are_a_girl"
    5.         char *p=a; 
    6.         char **ptr=&p; 
    7.         //printf("p=%d\n",p); 
    8.         //printf("ptr=%d\n",ptr); 
    9.         //printf("*ptr=%d\n",*ptr); 
    10.         printf("**ptr=%c\n",**ptr); 
    11.         ptr++; 
    12.         //printf("ptr=%d\n",ptr); 
    13.         //printf("*ptr=%d\n",*ptr); 
    14.         printf("**ptr=%c\n",**ptr); 
    15.     } 


     

    误区一、输出答案为Y 和o
    误解:ptr 是一个char 的二级指针,当执行ptr++;时,会使指针加一个sizeof(char),所以输出如上结果,这个可能只是少部分人的结果.
    误区二、输出答案为Y 和a误解:ptr 指向的是一个char *类型,当执行ptr++;时,会使指针加一个sizeof(char *)(有可能会有人认为这个值为1,那就会得到误区一的答案,这个值应该是4,参考前面内容), 即&p+4; 那进行一次取值运算不就指向数组中的第五个元素了吗?那输出的结果不就是数组中第五个元素了吗?答案是否定的.
    正解: ptr 的类型是char **,指向的类型是一个char *类型,该指向的地址就是p的地址(&p),当执行ptr++;时,会使指针加一个sizeof(char*),即&p+4;那*(&p+4)指向哪呢,这个你去问上帝吧,或者他会告诉你在哪?所以最后的输出会是一个随机的值,或许是一个非法操作.

    总结一下:
    一个指针ptrold 加(减)一个整数n 后,结果是一个新的指针ptrnew,ptrnew 的类型和ptrold 的类型相同,ptrnew 所指向的类型和ptrold所指向的类型也相同。ptrnew 的值将比ptrold 的值增加(减少)了n 乘sizeof(ptrold 所指向的类型)个字节。就是说,ptrnew 所指向的内存区将比ptrold 所指向的内存区向高(低)地址方向移动了n 乘sizeof(ptrold 所指向的类型)个字节。指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。
     

    三、运算符&和*

    这里&是取地址运算符,*是间接运算符。
    &a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
    *p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
     

    例六:

    1. int a=12; int b; int *p; int **ptr; 
    2. p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是 
    3. //int,指向的地址是a 的地址。 
    4. *p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是 
    5. //p 所指向的地址,显然,*p 就是变量a 
    6. ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个* 
    7. //在这里是int **。该指针所指向的类型是p 的类型,这 
    8. //里是int*。该指针所指向的地址就是指针p 自己的地址。 
    9. *ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针 
    10. //的类型和所指向的类型是一样的,所以用&b 来给*ptr  
    11. //值就是毫无问题的了。 
    12. **ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针, 
    13. //对这个指针再做一次*运算,结果是一个int 类型的变量。


     

    四、指针表达式

    一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
    下面是一些指针表达式的例子:
    例七:
     

    1. int a,b; 
    2. int array[10]; 
    3. int *pa; 
    4. pa=&a; //&a 是一个指针表达式。 
    5. Int **ptr=&pa; //&pa 也是一个指针表达式。 
    6. *ptr=&b; //*ptr &b 都是指针表达式。 
    7. pa=array
    8. pa++; //这也是指针表达式。

    例八:

    1. char *arr[20]; 
    2. char **parr=arr; //如果把arr 看作指针的话,arr 也是指针表达式 
    3. char *str; 
    4. str=*parr; //*parr 是指针表达式 
    5. str=*(parr+1); //*(parr+1)是指针表达式 
    6. str=*(parr+2); //*(parr+2)是指针表达式

    由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
    好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。在例七中,&a 不是一个左值,因为它还没有占据明确的内存。*ptr 是一个左值,因为*ptr 这个指针已经占据了内存,其实*ptr 就是指针pa,既然pa 已经在内存中有了自己的位置,那么*ptr 当然也有了自己的位置。
     

    五、数组和指针的关系

    数组的数组名其实可以看作一个指针。看下例:
     

    例九:

    1. int array[10]={0,1,2,3,4,5,6,7,8,9},value; 
    2. value=array[0]; //也可写成:value=*array; 
    3. value=array[3]; //也可写成:value=*(array+3); 
    4. value=array[4]; //也可写成:value=*(array+4);

    上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此*array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以*(array+3)等于3。其它依此类推。
     

    例十:

    1. char *str[3]={ 
    2.     "Hello,thisisasample!"
    3.     "Hi,goodmorning."
    4.     "Helloworld" 
    5. }; 
    6. char s[80] 
    7. strcpy(s,str[0]); //也可写成strcpy(s,*str); 
    8. strcpy(s,str[1]); //也可写成strcpy(s,*(str+1)); 
    9. strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));

    上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char *。
    *str 也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
    str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
    *(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'

    下面总结一下数组的数组名(数组中储存的也是数组)的问题:
    声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:
    第一,它代表整个数组,它的类型是TYPE[n];
    第二,它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
    在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。
    表达式array+n(其中n=0,1,2,.....)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
     

    例十一:

    1. int array[10]; 
    2. int (*ptr)[10]; 
    3. ptr=&array;

    上例中ptr 是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
    本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?
    答案是前者。例如:
    int(*ptr)[10];
    则在32 位程序中,有:
    sizeof(int(*)[10])==4
    sizeof(int[10])==40
    sizeof(ptr)==4
    实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。

     

    六、指针和结构类型的关系

    可以声明一个指向结构类型对象的指针。
     

    例十二:

    1.     struct MyStruct 
    2.     { 
    3.         int a; 
    4.         int b; 
    5.         int c; 
    6.     }; 
    7.     struct MyStruct ss={20,30,40}; 
    8.     //声明了结构对象ss,并把ss 的成员初始化为2030 40 
    9.     struct MyStruct *ptr=&ss
    10.     //声明了一个指向结构对象ss 的指针。它的类型是 
    11.     //MyStruct *,它指向的类型是MyStruct 
    12.     int *pstr=(int*)&ss; 
    13.     //声明了一个指向结构对象ss 的指针。但是pstr  
    14.     //它被指向的类型ptr 是不同的。 

    请问怎样通过指针ptr 来访问ss 的三个成员变量?
    答案:
    ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者
    ptr->b;
     

    ptr->c;

     

    又请问怎样通过指针pstr 来访问ss 的三个成员变量?
    答案:
    *pstr; //访问了ss 的成员a。
    *(pstr+1); //访问了ss 的成员b。
    *(pstr+2) //访问了ss 的成员c。
     

    虽然我在我的MSVC++6.0 上调式过上述代码,但是要知道,这样使用pstr 来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: (将结构体换成数组)

     

    例十三:

    1. int array[3]={35,56,37}; 
    2. int *pa=array
    3. //通过指针pa 访问数组array 的三个单元的方法是: 
    4. *pa; //访问了第0 号单元 
    5. *(pa+1); //访问了第1 号单元 
    6. *(pa+2); //访问了第2 号单元

    从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
    所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。
    所以,在例十二中,即使*pstr 访问到了结构对象ss 的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a 和成员b 之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。
    不过指针访问结构成员的正确方法应该是象例十二中使用指针ptr 的方法。



     

    七、指针和函数的关系

    可以把一个指针声明成为一个指向函数的指针。
    int fun1(char *,int);
    int (*pfun1)(char *,int);
    pfun1=fun1;
    int a=(*pfun1)("abcdefg",7); //通过函数指针调用函数。
    可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
     

    例十四:

    1.     int fun(char *); 
    2.     inta; 
    3.     char str[]="abcdefghijklmn"
    4.     a=fun(str); 
    5.     int fun(char *s) 
    6.     { 
    7.         int num=0
    8.         for(int i=0;;) 
    9.         { 
    10.             num+=*s;s++; 
    11.         } 
    12.         return num; 
    13.     } 

    这个例子中的函数fun 统计一个字符串中各个字符的ASCII 码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s 后,实际是把str 的值传递给了s,s 所指向的地址就和str 所指向的地址一致,但是str 和s 各自占用各自的存储空间。在函数体内对s 进行自加1 运算,并不意味着同时对str 进行了自加1 运算。


     

    八、指针类型转换

    当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
     

    例十五:

    1. float f=12.3
    2. float *fptr=&f; 
    3. int *p;

    在上面的例子中,假如我们想让指针p 指向实数f,应该怎么办?
    是用下面的语句吗?
    p=&f;
    不对。因为指针p 的类型是int *,它指向的类型是int。表达式&f 的结果是一个指针,指针的类型是float *,它指向的类型是float。
    两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0 上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
    p=(int*)&f;
    如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP *TYPE, 那么语法格式是: (TYPE *)p;
    这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE *,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。
    而原来的指针p 的一切属性都没有被修改。(切记)
    一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,必须保证类型一致,否则需要强制转换
     

    例十六:

    1.     void fun(char*); 
    2.     int a=125,b; 
    3.     fun((char*)&a); 
    4.     void fun(char*s) 
    5.     { 
    6.         charc; 
    7.         c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; 
    8.         c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; 
    9.     } 

    注意这是一个32 位程序,故int 类型占了四个字节,char 类型占一个字节。函数fun 的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a 的结果是一个指针,它的类型是int *,它指向的类型是int。形参这个指针的类型是char *,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int *类型到char *类型的转换。
    结合这个例子,我们可以这样来
    想象编译器进行转换的过程:编译器先构造一个临时指针char *temp,然后执行temp=(char *)&a,最后再把temp 的值传递给s。所以最后的结果是:s 的类型是char *,它指向的类型是char,它指向的地址就是a 的首地址。
    我们已经知道,指针的值就是指针指向的地址,在32 位程序中,指针的值其实是一个32 位整数。
     

    那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:

    1.     unsigned int a; 
    2.     TYPE *ptr; //TYPE intchar 或结构类型等等类型。 
    3.     a=20345686
    4.     ptr=20345686; //我们的目的是要使指针ptr 指向地址20345686 
    5.       
    6.     ptr=a; //我们的目的是要使指针ptr 指向地址20345686 
    7.     //编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法: 
    8.     unsigned int a; 
    9.     TYPE *ptr; //TYPE intchar 或结构类型等等类型。 
    10.     a=N //N 必须代表一个合法的地址; 
    11.     ptr=(TYPE*)a //呵呵,这就可以了。 

    严格说来这里的(TYPE *)和指针类型转换中的(TYPE *)还不一样。这里的(TYPE*)的意思是把无符号整数a 的值当作一个地址来看待。上面强调了a 的值必须代表一个合法的地址,否则的话,在你使用ptr 的时候,就会出现非法操作错误。想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
     

    例十七:

    1. int a=123,b; 
    2. int *ptr=&a; 
    3. char *str; 
    4. b=(int)ptr; //把指针ptr 的值当作一个整数取出来。 
    5. str=(char*)b; //把这个整数的值当作一个地址赋给指针str

    现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。



     

    九、指针的安全问题

    看下面的例子:
     

    例十八:

    1. char s='a'
    2. int *ptr; 
    3. ptr=(int *)&s; 
    4. *ptr=1298

    指针ptr 是一个int *类型的指针,它指向的类型是int。它指向的地址就是s 的首地址。在32 位程序中,s 占一个字节,int 类型占四个字节。最后一条语句不但改变了s 所占的一个字节,还把和s 相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。
    让我们再来看一例:
     

    例十九:

    1. char a; 
    2. int *ptr=&a; 
    3. ptr++; 
    4. *ptr=115;

    该例子完全可以通过编译,并能执行。但是看到没有?第3 句对指针ptr 进行自加1 运算后,ptr 指向了和整形变量a 相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。
    而第4 句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
    在指针的强制类型转换:ptr1=(TYPE *)ptr2 中,如果sizeof(ptr2的类型)大于sizeof(ptr1 的类型),那么在使用指针ptr1 来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2 的类型) 小于sizeof(ptr1 的类型),那么在使用指针ptr1 来访问ptr2 所指向的存储区时是不安全的。至于为什么,读者结合例十八来想一想,应该会明白的。

     

     

     

    展开全文
  • C语言指针数组和数组指针详解

    千次阅读 2022-04-12 21:07:45
    C语言基础-第一个C程序 C语言基础-简单程序分析 VS2019编写简单的C程序示例 简单示例,VS2019调试C语言程序 C语言基础-基本算法 C语言基础-数据类型 C语言中的输入输出函数 C语言流程控制语句 C语言数组——一维数组...

    C语言文章更新目录

    C语言学习资源汇总,史上最全面总结,没有之一
    C/C++学习资源(百度云盘链接)
    计算机二级资料(过级专用)
    C语言学习路线(从入门到实战)
    编写C语言程序的7个步骤和编程机制
    C语言基础-第一个C程序
    C语言基础-简单程序分析
    VS2019编写简单的C程序示例
    简单示例,VS2019调试C语言程序
    C语言基础-基本算法
    C语言基础-数据类型
    C语言中的输入输出函数
    C语言流程控制语句
    C语言数组——一维数组
    C语言数组——二维数组
    C语言数组——字符数组
    C语言中常用的6个字符串处理函数
    精心收集了60个C语言项目源码,分享给大家
    C语言核心技术——函数
    C代码是怎样跑起来的?
    C语言实现字符串的加密和解密
    C语言——文件的基本操作
    使用C语言链表创建学生信息并且将信息打印输出
    图解C语言冒泡排序算法,含代码分析
    实例分析C语言中strlen和sizeof的区别
    开发C语言的3款神器,VS2019、VScode和IntelliJ Clion
    动图图解C语言选择排序算法,含代码分析
    动图图解C语言插入排序算法,含代码分析

    C语言实例专栏(持续更新中…)

    首先来思考一个问题

    什么是指针数组?什么是数组指针?

    在这里插入图片描述

    单从字面上来看似乎很难分清它们是什么,我们先来看看指针数组和数组指针的定义。

    指针数组:数组类型 *数组名[数组长度];

    例:int *parr[5];

    数组指针:类型名 (*指针名)[数组长度];

    例:int (*parr)[5];

    现在我们再来通过上面的定义来分析一下指针数组和数组指针。

    int *parr[5];因为优先级的关系,parr先与[]结合,说明parr是一个数组,然后再与*结合说明数组parr的元素是指向整型数据的指针。元素分别是parr[0],parr[1]...parr[4],相当于定义了5个整形指针变量。所以parr就是数组元素是指针的数组,本质为数组。

    int (*parr)[5];``parr先与*结合,形成一个指针,该指针指向的是有5个整形元素的素组,parr就是指向数组元素地址的指针,本质为指针。

    这下对指针数组和数组指针应该有了初步的了解了吧,接下来我们来通过一段代码进一步了解一下。

    /*
     * @author: 冲哥
     * @date: 2022/4/12
     * @description:数组指针、指针数组
     * @公众号:C语言中文社区
     */
    #include <stdio.h>
    
    int main() {
        int arr[4][4] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
        int(*p1)[4];   //数组指针
        int* p2[4];    //指针数组
    
        p1 = arr;
        printf("使用数组指针的方式访问二维数组arr\n");
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                printf("arr[%d][%d]=%d\t", i, j, *(*(p1 + i) + j));
            }
            printf("\n");
        }
    
        printf("\n使用指针数组的方式访问二维数组arr\n");
        for (int k = 0; k < 4; ++k) {
            p2[k] = arr[k];
        }
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                printf("arr[%d][%d]=%d\t", i, j, *(p2[i] + j));
            }
            printf("\n");
        }
    
        return 0;
    }
    

    运行结果:
    在这里插入图片描述
    从结果可以看出我们成功的使用数组指针和指针数组的方式访问了二位数组。

    在分析数组指针和指针数组是如何访问二维数组之前,我们通过下面一段代码来学习一下表示二维数组每行起始地址的方式。

    /*
     * @author: 冲哥
     * @date: 2022/4/12
     * @description:数组指针、指针数组
     */
    #include <stdio.h>
    
    int main() {
        int arr[4][4] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
        for (int i = 0; i < 4; ++i) {
            printf("使用arr+i求得二维数组arr第%d行的起始地址为%d\n", i + 1, arr + i);
            printf("使用arr[i]求得二维数组arr第%d行的起始地址为%d\n", i + 1, arr[i]);
            printf("使用*(arr+i)求得二维数组arr第%d行的起始地址为%d\n", i + 1, *(arr + i));
            printf("使用&arr[i]求得二维数组arr第%d行的起始地址为%d\n", i + 1, &arr[i]);
        }
        return 0;
    }
    

    运行结果:
    在这里插入图片描述
    在上面的代码中,我们使用了4种方式来获得每行的起始地址,因此行起始地址的表示方式并不唯一。

    下面接着分析数组指针和指针数组是如何访问二维数组的,先看数组指针的访问方式。因为数组指针指向的是一个有4个整型元素的数组,所以可以把二维数组arr看成由4个元素arr[0],arr[1],arr[2],arr[3]组成,每个元素都是含有4个整型元素的一维数组,所以当在代码中使用p1=arr的时候,p1就指向了二维数组的第一行的首地址。在接下来的访问中,由于p1指向的类型是int [4],所以从p1p1+1的变化值为44个字节,即p1+1=9435716。从前面的运行结果中可以发现,p1+1刚好指向第二行的起始地址。通过p1+i刚好能够取遍每行的起始地址,有了每行的起始地址之后,就可以通过*(*(p1+i)+j)来取出二维数组中每行的每一个元素。指针数组的访问方式要更容易一些,因为定义的指针数组p2由4个元素p2[0],p2[1], p2[2], p2[3]组成,每个元素都是一个整型指针,所以只需要在程序中取出每行的起始地址并放到p2指针数组对应的元素中,就可以访问二维数组arr中的元素了。

    所以,在程序中使用指针数组和数组指针的时候,必须对它们有清晰的认识,要知道它们的本质是什么,以及如何使用。

    参考书籍:C语言进阶:重点、难点与疑点解析

    如果您觉得本篇文章对您有帮助,请转发给更多的人
    C语言中文社区】是一个C语言/C++视频教程、学习笔记、电子书、计算机二级资料等专注于C语言/C++编程学习者的干货知识分享平台,精选深度文章,分享优秀干货类、技能类的学习资源,帮助学习中的你。在这里插入图片描述

    展开全文
  • C语言指针总结

    2016-11-29 11:05:21
    指针C语言中的难点,也是C语言中的精华所在,本文档列举了指针使用中的常见注意事项及易混淆知识点的对比,为C语言的初学者掌握指针提供了清晰的思路。
  • 作 业使用指针数组函数完成 1. 编写一个通用函数该函数可以实现判断一个含有五位数字的整数是否是回文数回文数的含义是从左向右与从右向左看数是相同的如23732是回文数而23564则不是编写主程序调用该函数实现求所有5...
  • 这篇文章全面分析C语言指针重难点,并分为前后篇幅,如果你是还没有接触过指针的小白,请先看完这篇文章再返回进阶 初阶指针 文章目录一、字符指针二、指针数组三、数组指针数组名实战运用 一、字符指针 ...
  • c语言指针用法详解,通俗易懂超详细!

    千次阅读 多人点赞 2021-07-18 00:10:30
    文章转自:无际单片机大家好,我是无际。今天给大家来讲解一下指针。我会由浅到深,最后结合实际应用讲解,让大家学会指针的同时,知道大佬们都用指针来干嘛!长文预警!全文大约5200多字,学指针看...
  • 指针C语言的精髓,但是很多初学者往往对于指针的概念并不深刻,以至于学完之后随着时间的推移越来越模糊,感觉指针难以掌握,本文通过简单的例子试图将指针解释清楚,今天的重点有几个方面: 什么是指针 数组和...
  • 征服C指针,带你进入C语言指针世界,让你彻底征服指针。
  • C语言指针基础

    2018-12-15 09:04:51
    C语言中的指针其实并没有那么可怕,我上传了一份C语言指针基础课件。欢迎大家下载。
  • C语言指针传参

    2022-01-28 13:59:15
    一级指针传参: 一级指针传参,可以使用数组名,代表的是数组元素首地址,如test(arr); 可以使用指针int*p=arr;test(p);也可以使用test(&arr) test为调用的函数或自定义的函数,本质传出的是“数组元素首地址...
  • 本文针对C语言指针问题进行了详细的解析,感性趣的朋友可以看看。
  • c语言 指针的赋值

    2022-05-04 18:57:22
    如果一个指针指向一个变量的地址,如何通过指针来改变该变量的值呢? 一、指针的赋值 例如:int * p; int a=3, b=4; p= &a; / /指针 p 指向变量 a 的地址。 p=&b; / /指针 p 重新指向变量 b 的地址。 二、...
  • C语言指针溢出

    千次阅读 2022-05-04 13:44:04
    C语言指针不安全 #include<stdio.h> int main(){ // char a[] = "nihaoma?"; // char *p = a; // // printf("%c",a[8]); // printf("%s",p); int a[2] = {0,1},b = 2; printf("%d\n",&b); int *p...
  • 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,...
  • 新手在C语言的学习过程中遇到的最头疼的知识点应该就是指针了,指针C语言中有非常大的用处。下面我就带着问题来写下我对于指针的一些理解。 指针是什么?   指针本身是一个变量,它存储的是数据在内存中的地址...
  • C语言指针详解

    2019-01-16 16:45:51
    我们今天来攻克C 语言指针。 我并不打算使用过于官方、正统的语言来讲解指针。因为如果我这样做,就失去 了做这个教程的意义。如果需要,大家完全可以从各大教材、网站找到对指针正 规的解释。但无疑,这种正规、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,517,583
精华内容 607,033
关键字:

c指针