指针的应用练习(数组与指针的关系)

简介: 指针的应用练习(数组与指针的关系)

如果对指针不是那么熟悉,我这里有几篇指针相关入门,不知道能不能帮助到你


http://t.csdn.cn/BbVwT


http://t.csdn.cn/eqBng


http://t.csdn.cn/hwNXp


看完后,检测一下这两段代码是否能透彻理解


(1)


#include<stdio.h>
int main()
{
int a=100;
int *p;
p=&a;
printf("%d",*p);
}
其输出为100


(2)


int a = 100;
int   *p1 = &a;  // 一级指针,指向普通变量
int  **p2 = &p1; // 二级指针,指向一级指针
int ***p3 = &p2; // 三级指针,指向二级指针

如果一个指针变量 p1 存储的地址,是另一个普通变量 a 的地址,那么称 p1 为一级指针

如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针

如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针

以此类推,p2、p3等指针被称为多级指针

一.数组与指针的表示

int a[3]; // 此处,a 代表整个数组


printf("%d\n", sizeof(a)); // 此处,a 代表整个数组


printf("%p\n", &a); // 此处,a 代表整个数组,此处为整个数组的地址


int *p = a; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]


p = a + 1; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]


function(a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]


scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]


在大多情况下,指针都是指向数组的首元素,可以这么说,数组运算实际上就是指针运算。


补充:

字符串实质上是一个匿名数组,应用和数组很类似:


printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组

printf("%p\n", &"abcd");         // 此处 "abcd" 代表整个数组


printf("%c\n", "abcd"[1]);       // 此处 "abcd" 代表匿名数组的首元素地址

char *p1 = "abcd";                // 此处 "abcd" 代表匿名数组的首元素地址

char *p2 = "abcd" + 1;         // 此处 "abcd" 代表匿名数组的首元素地址


a[i] = 100;  等价于  *(a+i) = 100;


所以易得,a[i]=100,也与下面几项等价


a[i] = 100;

*(a+i) = 100;

*(i+a) = 100;

 i[a] = 100;


例:


用三种等价的方法表示a[0][0]


&a[0][0]


//a[0]是首元素a[0][0]的地址


a[0]


//a[0]=*(a+0)=*(a)=*a


*a


二. 数组与指针语法

数组语法剖析

任意的数组,不管有多复杂,其定义都由两部分组成。

第1部分:说明元素的类型,可以是任意的类型(除了函数)

第1部分:说明数组名和元素个数

int a[4];                     // 第2部分:a[4]; 第1部分:int


int b[3][4];                 // 第2部分:b[3]; 第1部分:int [4]


int c[2][3][4];             // 第2部分:c[2]; 第1部分:int [3][4]


int *d[6];                   // 第2部分:d[6]; 第1部分:int *


int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)


指针语法剖析


任意的指针,不管有多复杂,其定义都由两部分组成。

第1部分:指针所指向的数据类型,可以是任意的类型

第2部分:指针的名字

char   (*p1);                   // 第2部分:*p1; 第1部分:char;

char  *(*p2);                  // 第2部分:*p2; 第1部分:char *;

char **(*p3);                 // 第2部分:*p3; 第1部分:char **;

char   (*p4)[3];             // 第2部分:*p4; 第1部分:char [3];

char   (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);


来一个较为复杂的


int (*parr3[10])[5];//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int类型


分析一下


parr3[10]提出来,是一个数组,该数组有是个元素,剩下:

int(*)[5]:代表的是一个数组指针数组指针指向的数组有5个元素,每个元素int型


例:


定义一个整型数 i

定义一个指向整型数的指针 p

定义一个指向整型指针的指针 k

定义一个有 3 个整型数的数组 a

定义一个有 3 个整型指针的数组 b

定义一个指向有 3 个整型元素的数组的指针 q

定义一个指向函数的指针 r,该函数有一个整型参数并返回一个整型

参考代码

1. int i;

2. int *p;

3. int **k;

4. int a[3];

5. int *b[3];

6. int (*q)[3];

7. int (*r)(int);


三.指针运算

指针p自增


#include <stdio.h>
int main(void)
{
    int a[] = {1, 2, 3, 4};
    int i, *p;
    for(i=0, p=a; i<4; i++, p++)
    {
        printf("%d %d\n", a[i], *p);
    }
    return 0;
}

注:这里p=a,表示让指针指向数组a的首元素1,再p++,使指针p不断自增


得到输出结果为


1 1

2 2

3 3

4 4


*p和*(p+1)


#include<stdio.h>
int main()
{
    int *p;
    int a[2][2] = {{1, 0}, {2, 3}};
    p= a[0];
    printf("%d, %d", *p, *(p+1));
    return 0; 
}

p指向数组的首元素a[0][0],所以*p=a[0][0]的值,*(p+1)=p[0][1]


所以输出为:1,0


重要知识点,先来看代码


将字符串中的小写字母转换为大写字母


#include <stdio.h>
#include <limits.h>
#include <ctype.h>
void upper_case(char str[])
{
    int step = 'a' - 'A';
    for(int i = 0; i<sizeof(str)/sizeof(str[0]); i++)
    {
        if(islower(str[i]))
            str[i] -= step;
    }
}
int main(void)
{
    char str[] = "abcdefghijklnmopqrstuvwxyz";
    printf("原数组:%s\n", str);
    upper_case(str);
    printf("转换后:%s\n", str);
}


这段代码有一个错误,在函数upper_case中,sizeof(str)/sizeof(str[0])的结果并不是数组长度,而是指针的大小。


这是因为数组在除了定义和sizeof语句之外,均会被视为指向其首元素的指针,因此在上述代码中, upper_case(str) 中的str是一个指针,而非数组,等价于:


upper_case(&str[0]);


所以str永远都是指针,而非数组,所以sizeof(str)无法计算得数组大小


在这里,可以将数组长度作为额外的参数传递给函数upper_case,或者使用一个特殊的字符(如'\0')作为数组的结束标志,然后在循环中检查该标志来确定数组的长度。


#include <stdio.h>
#include <limits.h>
#include <ctype.h>
void upper_case(char str[])
{
    int step = 'a' - 'A';
    for(int i = 0; str[i] != '\0'; i++)
    {
        if(islower(str[i]))
            str[i] -= step;
    }
}
int main(void)
{
    char str[] = "abcdefghijklnmopqrstuvwxyz";
    printf("原数组:%s\n", str);
    upper_case(str);
    printf("转换后:%s\n", str);
    return 0;
}

四.结构体中指针的应用

struct node
{
    /* 结构体的其他成员 */
    // 成员1
    // 成员2
    // ... ...
   
    int  len;
    char data[0];
};
// 给结构体额外分配 10 个字节的内存。
struct node *p = malloc(sizeof(struct node) + 10);
p->len = 10;
// 额外分配的内存可以通过 data 来使用
p->data[0] ~ p->data[9]


五.进阶练习

(1)编写一个程序,测试当前平台的字节序


#include <stdio.h>
int main(void)
{
    // 定义一个4字节的整型数据
    int a = 0x12345678;
    // 定义一个char型指针指向最低地址
    char *p = &a;
    // 将最低字节数据打印出来
    // 如果是0x78,那就代表最低地址存储了低有效位,是小端序
    // 如果是0x12,那就代表最低地址存储了高有效位,是大端序
    printf("%#x\n", *p);
}

(2)(数组下标运算、指针运算、内存操作)


注:这里的sizeof(long)是四个字节


#include <stdio.h>
int main(void)
{
    long  a[4] = {1, 2, 3, 4};
    long  *p1=(long *)(&a+1);
    long  *p2=(long *)((long)a+1);
    printf("%lx\n", p1[-1]);
    printf("%lx\n", *p2);
   
    return 0;
}

解析:


首先,&a是整个数组的地址,因此&a+1实际上是向后偏移了16个字节,因此 p1 指向了数组边界。(引用博主@赵日天他大哥的图片)



然后输出 p1[-1] 等价于 *(p1-1),因为 p1 是 long 型指针,因此 p1-1 就向前偏移 sizeof(long) 个字节,也就是指向了 a[3],即4。


其次,(long)a+1 是一个纯整数运算(因为a被强转为long了),因此 long型指针p2 就指向了long型数据 a[0] 的第二个字节


最后打印 *p2 时,由于 p2 是一个 long 型指针,系统会从 a[0] 的第二个字节开始,取出 sizeof(long) 个字节出来作为 long 型数据来解释,因此最后输出的结果是 a[0] 的高位三字节和 a[1] 低位一字节的数据



a[0]的第二个字节开始,往后数四个字节,就是a[0] 的高位三字节和 a[1] 低位一字节的数据


(3)声明一个二维 int 型数组 a,再声明另一个一维数组指针数组 b,使该数组 b 的每一个指针分别指向二维数组 a 中的每一个元素(即每一个一维数组),然后利用数组 b 计算数组 a 的和。


注:“指针数组”指的是装了很多指针的数组,“数组指针”指的是指向数组的指针,“数组指针数组”指的是装了很多数组指针的数组。


#include <stdio.h>
int main(void)
{
  int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
  int (*b[2])[3];
    1. *b[2]//数组b有两个元素,每个元素都是一个指针,与int (*b)[2]进行区别,这里是包含两个整形元素的指针
    2. int (*b[2])[3]//该数组是包含两个指针的数组,每个指针指向3个int类型元素   
  b[0] = &a[0];//第一个指针指向a[0]的地址
  b[1] = &a[1];//第二个指针指向a[1]的地址
  int i, j, sum=0;//初始化sum
  for(i=0; i<2; ++i)
  {
  for(j=0; j<3; ++j)
  {
    sum += (*b[i])[j];//作和
  }
  }
  printf("sum: %d\n", sum);
  return 0;

}


(4)编写一个程序,去掉给定字符串中重复的字符。


#include <stdio.h>
#include <string.h>
void strip(char *str)
{
    if(str == NULL)
        return;
    int i, j;
    char *p = str;
    // 将所有重复的字符标记为-1
    for(i=1; str[i] != '\0'; i++)
    {
        for(j=0; j<i; j++)
        {
            if(p[j] == str[i])
            {
                str[i] = -1;
                break;
            }
        }
    }
    // 找到第一个-1的位置 redun
    // 找到紧随 redun 之后的正常字符的位置 common
    int redun = 0, common = 0;
    for(i=1; str[i] != '\0'; i++)
    {
        if(str[i] != REDUNDANT)
            continue;
        redun = i;
        while(str[i] == REDUNDANT)
            i++;
        common = i;
        break;
    }
    // 若没有重复的字符,则返回
    if(redun == 0)
        return;
    // 将所有正常字符往前移
    while(str[common] != '\0')
    {
        if(str[common] == -1)
        {
            common++;
            continue;
        }
        str[redun++] = str[common];
        str[common++] = -1;
    }
    str[redun] = '\0';
}
int main(void)
{
    // 输入待处理字符串
    char buf[64];
    fgets(buf, 64, stdin);
    //对于fgets的应用可以看
//http://t.csdn.cn/mDMBb
    strip(buf);//删除buf字符串
    printf("%s", buf);
   
    return 0;
}


strip示例




目录
相关文章
|
7天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
15 1
|
16天前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
16天前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。
|
16天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
22天前
魔法指针 之 二级指针 指针数组
魔法指针 之 二级指针 指针数组
15 1
|
2月前
|
传感器 物联网 大数据
C 指针在物联网的应用
在物联网(IoT)中,C 语言及其指针功能广泛应用于嵌入式系统。C 指针在内存管理、设备驱动、数据结构处理、传感器通信等方面发挥关键作用,如动态分配内存、直接访问硬件寄存器、传递复杂数据结构等,有效提升了资源受限环境下的性能和灵活性。通过函数指针和省电模式管理,还能实现事件驱动编程和节能目标,使 C 语言成为 IoT 开发的重要工具。
64 12
|
25天前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
17 0
|
1月前
|
编译器 C语言
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
|
2月前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
3月前
|
搜索推荐 C语言
指针与数组
指针与数组
57 9