​【程序猿必备:指针与数组的高级技能秘籍】(上)

简介: ​【程序猿必备:指针与数组的高级技能秘籍】

指针和数组的关系


  • 指针指的是指针变量,不是数组,指针变量的大小是4/8个字节,是专门来存放地址的。
  • 数组也不是指针,数组是一块连续的空间,存放一组相同类型的数据的。


没有关系,但是它们之间有比较相似的地方


以指针的形式访问和以数组的形式访问


#include <stdio.h>
#include <string.h>
int main()
{
    //这是指针
  char* str = "abcdef"; //str指针变量在栈上保存,“abcdef”在字符常量区,不可被修改
    //这是数组
  char arr[] = "abcdef"; //整个数组都在栈上保存,可以被修改
    //访问指针
  //1. 以指针的形式访问指针和以下标的形式访问指针
  printf("以指针的形式访问指针和以下标的形式访问指针\n");
  int len = strlen(str);
  for (int i = 0; i < len; i++) {
    printf("%c\t", *(str + i));
    printf("%c \n", str[i]);
  }
  printf("\n");
    //访问数组
  //2. 以指针的形式访问数组和以下标的形式访问数组
  printf("以指针的形式访问数组和以下标的形式访问数组\n");
  len = strlen(arr);
  for (int i = 0; i < len; i++) {
    printf("%c\t", *(arr + i));//数组名在大部分表达式中,代表的是首元素的地址
    printf("%c \n", arr[i]);
  }
  printf("\n");
  return 0;
}


结论:指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性。但是具有相似性,不代表是一个东西或者具有相关性。


C为何要这样设计?


//我们初步阅读一下下面代码
#include <stdio.h>
#include <string.h>
//这里两种写法都行,大家都知道,函数传参是要发生降维的(为什么?)
//降维成指针。
//换句话说,arr在main里面是数组,传入InitArr函数之后,就成了数组首元素的指针变量。
//void InitArr(int arr[], int n)//这里的arr就不是数组了,它成了数组首元素的指针变量。
//降维后,此时arr就没有进行每个元素的临时拷贝,只进行了首元素地址的临时拷贝。
//在c中,任何函数调用,只要有形参实例化,必定形成临时拷贝
void InitArr(int* arr, int n)
{
    //sizeof(arr)结果为4 - 是32位平台下指针的大小。
  for (int i = 0; i < n; i++) 
  {
    *(arr + i) = i; //以指针的形式访问
  }
}
int main()
{
  int arr[5] = {1,2,3,4,5};
    //sizeof(arr)结果为20 - 是数组的大小。
    int num = sizeof(arr)/sizeof(arr[0]);//求数组元素的个数,[]为什么为0?因为0下标绝对存在
  InitArr(arr, num);
  for (int i = 0; i < num; i++) {
    printf("%d\n", arr[i]); //以数组的形式访问
  }
  return 0;
}
//上面代码其实没有什么问题,不过,不知道大家发现没有,\
如果没有将指针和数组元素访问打通,\
那么在C中(面向过程,函数是核心概念,离不开定义与调用函数)\
如果有大量的函数调用且有大量数组传参,会要求程序员进行各种访问习惯的变化。\
只要是要求人做的,那么就有提升代码出错的概率和调试的难度。
//假设指针和数组的访问方式方式不通用,\
程序员就需要不断地在不同的代码片段处,进行习惯的切换
//所以干脆,C将指针和数组的访问方式打通,让程序员在函数内,\
也好像使用数组那样进行元素访问,本质是减少了编程难度!
//打通之后,* 和[ ]两个操作符都可以对数组进行访问
//故整个降维过程,对于使用者是透明的,使用者不需要了解降维过程。


函数传参是要发生降维的


为什么?


       复杂数据类型通常需要较大的内存空间和更复杂的操作,直接传递复杂数据类型可能会导致效率低下,而且在栈上分配大块的内存也可能导致栈溢出的问题。


降维成什么?


       函数传参中的“降维”是指将复杂数据类型(如数组)降维成为指向其内部元素的指针来传递的过程。

比如:int arr[10];会降维成int类型的指针。


a 和 &a的区别  ----- 复习 + 练习


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


结论:&a叫做数组的地址,a做右值叫做数组首元素的地址,本质是类型不同,进而进行+-计算步长不同

  • &a:+1表示步长为sizeof(arr)。
  • a:+1表示步长为sizeof(arr[0])。


指针数组和数组指针


  • 指针数组:是由若干个指针所组成的数组,每个指针指向一个特定类型的变量。在指针数组中,每个元素都是一个指针变量。
  • 数组指针:是一个指向数组的指针变量。


那么下面的到底那个是数组指针,那个是指针数组呢?

(A)、int* p1[10];
(B)、int(*p2)[10];


提示:


  • [ ]的优先级大于 *
  • ( )的优先级最大


那要怎么使用它们呢?


  • 指针数组
#include <stdio.h>
int main()
{
  int* p1[5]; // 声明一个指针数组,数组中包含 5 个指向 int 类型变量的指针变量
  int a = 1, b = 2, c = 3, d = 4, e = 5;
  p1[0] = &a; // 将指针变量 p1[0] 设置为变量 a 的地址
  p1[1] = &b;
  p1[2] = &c;
  p1[3] = &d;
  p1[4] = &e;
  //这里的[]优先级比*高
  printf("%d %d %d %d %d\n", *p1[0], *p1[1], *p1[2], *p1[3], *p1[4]); // 输出 1 2 3 4 5
  return 0;
}


  • 数组指针
#include <stdio.h>
int main()
{
  int(*p2)[5]; // 声明一个数组指针,指向包含 5 个 int 类型变量的数组
  int a[5] = { 1, 2, 3, 4, 5 };
  p2 = &a; // 将指针变量 p 设置为数组 a 的首地址
  printf("%d %d %d %d %d\n", (*p2)[0], (*p2)[1], (*p2)[2], (*p2)[3], (*p2)[4]); // 输出 1 2 3 4 5
  return 0;
}


总结:


  • 整形指针数组,数组内部,后面可以放置任何类型(内置、结构体、联合体等)的内容。

       (A)、int* p1[10];

  • 整形数组指针,指针可以指向任何合法的类型变量。

       (B)、int(*p2)[10];


 我们发现我们在定义变量的都是先写变量的类型,然后再写变量名。


  • 提示:数组的数据类型是int [num]。
  • 数组指针一般使用的时候都是指向整个数组的地址。

//变量类型为char,变量名为c
char c;
//变量类型为int,变量名为a
int a;
//变量类型为double*,变量名为b
double* b;
//那我们定义数组应该是
int[5] d;
//定义指针数组应该是
int*[5] p1;
//定义数组指针应该是
int[5]* p2
//但是实际上是
//变量类型是int[5]变量名是d
int d[5];
//变量类型是int*[5],变量名是p1
int *p1[5];  // ->  指针数组
//变量类型是int[5]*,变量名是p2
int (*p2)[5];// ->  数组指针
//这是c语言的规定写法


不过我们这里可以用typedef来改这个规定


#include<stdio.h>
typedef int* p_1[5];
typedef int(*p_2)[5];
int main()
{
  //变量类型是int*[5],变量名是p1
  int* p1[5];  // ->  指针数组
  p_1 p3;
  //变量类型是int[5]*,变量名是p2
  int(*p2)[5];// ->  数组指针
  p_2 p4;
  return 0;
}


总结:

  • 指针数组的数据类型是int *[num]。
  • 数组指针的数据类型是int[num] *。
//这两个是什么意思?
//指针数组
int *p[4];
//数组指针数组
int(*p[4])[5];
//p先和[4]结合,是一个数组,可以把这里的p[4]整体理解为上面的p,只不过此时的p是数组
//数组指针
int (*p)[4]
//数组指针数组的指针
int(*(*p)[4])[5];
//p先和*结合,是一个指针,然后指向一个[4]的数组,数组里面的内容是指针,\
而这个指针是指向一个[5]的指针


地址的强制转化


  • 先回答问题,强制类型转换,究竟在做什么?---> 将一种数据类型强制转换成另一种数据类型,在强制类型转换中,数据的内容不会发生改变,只是改变了数据的解释方式。

  • 强制类型转换,和把字符串“1234”转化成int 1234的转化,有区别吗?---> 将字符串转换为整数是指将一个表示数字的字符串转换为整数类型,这种一定会改变数据本身,称为强制类型转化。

结论:强制类型转化,改变的是对特定内容的看待方式,在C中,就是只改变其类型,对数据的本身是不好发生任何变化的!

#include<stdio.h>
int main()
{
  int a = 0x11223344;
  printf("%x\n", *(&a));
    //将整型的地址强制转化为字符型的地址,查看输出结果?
  printf("%x\n", *((char*)&a));
  return 0;
}


  1. 这也符合我们之前的结论,将整形指针类型的&a强制转化为字符型指针类型的&a, 但是&a的内容不会发生改变,只是改变了&a的解释方式。
  2. 然后由于char类型每次只能访问一个字节,且访问的时候都是开辟空间的众多字节中地址最小的那个字节。
  3. 由于我们目前电脑的是小端模式,拿出的是权值最低的那个字节,即44。
#include <stdio.h>
struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
  printf("%p\n", p + 0x1);//0x100000 + 20(0x14) == 0x100014
  //此时的p经过强制类型转化后,不再是指针了,而是一个无符号长整型的数据
  printf("%p\n", (unsigned long)p + 0x1);//0x100000(无符号长整型) + 1 = 0x100001
  //强制转换为无符号整形指针,+1跨过sizeof(int)个字节
  printf("%p\n", (unsigned int*)p + 0x1);//0x100000 + 4(0x04) = 0x100004
  return 0;
}
#include <stdio.h>
int main()
{
  int a[4] = { 1, 2, 3, 4 };
  int* ptr1 = (int*)(&a + 1);
  int* ptr2 = (int*)((int)a + 1);
  printf("0x%x,0x%x\n", ptr1[-1], *ptr2);
  //数组[]内只能为正数,这里为负数说明这里是指针,等价于*(ptr1-1)
  return 0;
}


【程序猿必备:指针与数组的高级技能秘籍】(中):https://developer.aliyun.com/article/1424716

相关文章
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
3月前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
3月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
3月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
3月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
81 4
|
3月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
63 2
|
3月前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
53 3
|
3月前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
51 1
|
4月前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
4月前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。

热门文章

最新文章