【C语言进阶】指针的进阶(上)

简介: 【C语言进阶】指针的进阶(上)

前言

我们在C语言基础中,已经了解并认识到了指针的概念:

      1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间
       2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
       3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
       4. 指针的运算。

接下来我们来认识不一样的指针,更有深度的指针。


一、字符指针

字符指针,顾名思义是接收字符类型的指针,书写方式为:char *

一般使用:

//字符指针
int main()
{
  char a = 'w';
  char* p = &a;
  printf("%c\n", *p);
  return 0;
}

还有一种使用方法:

int main()
{
  //char a = "abcde";//字符类型不能接受字符串
  char* p = &a;
//字符类型char是 无法直接接收字符串的,而已用字符数组的形式接收,但是字符指针可以直接接收字符串,效果和字符数组类似
  char* p = "abcdef";//将字符串这个常量值,给字符指针p  得到的是a的地址  就是字符串首元素的地址
  printf("%s\n", p);//输出的时候p是字符串的地址  就相当于字符数组的形式进行输出
  return 0;
}

第二种使用方法的数据存放方式:

且使用的是常量字符串的时候,最好在  char*  前面加上const,因为本身常量是无法改变的,所以加上const,更加符合编程要求。

面试题(字符指针):

二、指针数组

顾名思义,指针数组,就是存放指针的数组,这样的数组是要有统一的存储指针的类型的。

如:

int* arr1[10];         //整形指针的数组
char *arr2[4];         //一级字符指针的数组
char **arr3[5];        //二级字符指针的数组

//指针数组
int main()
{
  //存放字符指针的数组
  const char* arr[3] = { "hello","bit","why" };
  for (int i = 0; i < 3; i++) {
    printf("%s\n", arr[i]);
  }
  return 0;
}
int main()
{
  int arr1[5] = { 1,2,3,4,5 };
  int arr2[5] = { 2,3,4,5,6 };
  int* arr[4] = { arr1,arr2 };
  //模拟二维数组
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 5; j++) {
      printf("%d", arr[i][j]);
    }//  或者是 *(arr[i]+j)
    printf("\n");
  }
  return 0;
}

三、数组指针

3.1数组指针定义

数组指针,其实是一个指针。

整型指针:int * pi  能够指向整形数据的指针。

字符指针:char *pc 能够指向字符数据的指针。

那么:

数组指针: int (*pi) [10]  能够指向数组的指针

int* pa [10];    //这个是指针数组
int (*pa)[10];  //这个才是数组指针
//因为  [] 的优先级比 * 大,所以要加括号
对  int (*pa)[10] 进行解释:

 

3.2.&数组名和数组名

&数组名和数组名,有什么区别:

我们知道arr是数组名,传参的时候,是表示首元素的地址。

int main()
{
  int a = 10;
  int* pa = &a;  //这个时候的去掉(*pa)得到的是a的类型 int
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    //&arr取出的是数组的地址,只有数组的地址才需要数组来接收
  int(*p)[10] = &arr;//【】优先级比 * 高 所以要加上括号 这个去掉(*p)得到的是 int [10] 
  //数组名  -  数组首元素的地址
  //&数组名 -  是数组的地址
  //数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样
  printf("%p\n", arr);//表示首元素的地址
  printf("%p\n", arr+1);//加4/8 在32/64的平台下
  printf("%p\n", &arr[0]);//表示首元素的地址
  printf("%p\n", &arr[0]+1);//加4/8 在32/64的平台下
  printf("%p\n", &arr);//表示整个数组的地址
  printf("%p\n", &arr+1);//&arr表示的是整个数组的地址 +40/80  在32/64位的平台下
  return 0;
}

所以,&arr和arr虽然数值是一样的,但是意义是不一样的。

实际上,&arr表示的是数组的地址,arr表示的是数组的首元素的地址

arr        ==        arr[0]        ==        &arr       (数值相同,都是数组首元素的地址)

arr+1        ==        arr[0]+1        !=        &arr+1        (arr+1、arr[0]+1是4/8个字节,&arr+1是跨越整个数组大小,+数组每个元素的字节 * 数组元素个数)

3.3数组指针的使用

可以使用数组指针访问二维数组,这里只是演示如何使用,并非只能如此。

//传统方法:
void print1(int arr[3][4], int r, int c) {
  for (int i = 0; i < r; i++) {
    for (int j = 0; j < c; j++) {
      printf("%d ", arr[i][j]);
    }
  }
}
//使用数组指针
void print2(int(*p)[4], int r, int c) {
  //去掉(*p)得到的是存储的数据的类型 int [4]  因为我们存储的是二维数组,。那么二维数组的每一行都可以看作是一个一维数组 即 int [4]
  for (int i = 0; i < r; i++) {
    for (int j = 0; j < c; j++) {
      printf("%d ", (*(p + i))[j]);
      //printf("%d ", p[i][j]);//  实际上 【】相当于 *()
    }
    printf("\n");
   }
}
int main()
{
  int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10 };
  //print1(arr, 3, 4);  
  //对于二维数组的数组名表示的是首元素的地址
  print2(arr, 3, 4);
  return 0;
}

以上咱学习了,指针数组和数组指针,下面进行分析,区分指针数组和数组指针

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

1.第一个明显的是一个整型数组,可以存放5个整型元素

2.这是一个指针数组,归根结底是一个数组,可以存放10个整型指针的数组

3.这是一个数组指针,归根结底是一个指针,存放的是有十个整型元素的数组

4.这是一个数组指针,首先这个是一个数组有十个元素,取出(*parr3【10】)剩下为 int  [5] 对应的是接收五个元素的数组的地址。

四、数组参数、指针参数

4.1一维数组传参

       使用一维数组传参的方式有哪些,什么样的形参可以接收一维数组?

void test1(int arr[]) {
  //使用  int arr[] 数组的形式是可以接收的
}
void test1(int arr[10]) {
  //当然也可以使用这样的形式接收 10 可带可不带
}
void test1(int* arr) {
  //使用整型指针的形式是可以接收整型一维数组的
}
void test2(int* arr[10]) {
  //指针数组  可以用指针数组的形式接收
}
void test2(int** arr) {
  //实参为指针数组,传递的是 int* arr[10]  ,加上 传递数组名表示,传递的是数组的地址,所以用二级指针接收是可以的
}
int main()
{
  int arr1[10] = { 0 };
  int* arr2[10] = { 0 };
  test1(arr1);
  test2(arr2);
  return 0;
}

4.2二维数组传参

//二维数组传参
void test(int arr[][4]) {
  //可以的  arr[]  相当于 (*arr)  也就是int(*arr)【4】
  //对应的只需要知道有几列就可以 第一个[]可以不写  第二个必须写
}
void test(int arr[][]) {
  //是不对的,第二个[]省略了
}
void test(int arr[3][4]) {
  //是可以的 
}
void test(int* arr) {
  //不可以,传递的是二维数组,也就是第一行数组的地址,不能用一级指针接收一个数组的内容
  //一级指针可以接收一维数组的地址,不能接收二维数组的地址
}
void test(int* arr[5]) {
  //是指针数组,这样也不能接收,二维数组
}
void test(int(*arr)[5]) {
  //二维数组看来指针方面 ,只能由数组指针可以接收了,【5】为列的个数
}
void test(int** arr) {
  //二级指针同理,传递的是二维数组,实际上是二维数组的第一行数组的地址,只能由数组指针来接收,二级指针不行
}
int main()
{
  int arr[3][4];
  test(arr);
  return 0;
}

4.3一级指针传参

4.4二级指针传参

对于以上类型传参有两个我认为比较容易混淆。

1.二维数组的传参,可以使用数组指针来接收         int(*pa)[10]

2.对于二维指针的接收,可以传参指针数组            int*arr[10]  

3.二维数组的传参的时候,需要用指针接收的时候只能用,数组指针 int(*pa)[10]

五、函数指针

根据上面各种指针所学,我们大概也知道函数指针应该是什么了,接下来,我来带大家看看到底什么是函数指针!

函数指针:存放函数地址的指针

如:        int test (int a) {};

函数指针为:        int(*pa)(int)  =  &test;

使用函数指针:       int num = pa(10);

说明&函数名和函数名的地址是一样的

下面是函数指针的使用方法,以及表示方法

//函数指针  - 函数指针是存放函数地址的指针
void add(int x, int y) {
  printf("%d \n", x + y);
}
int main()
{
  int (*pf1)(int, int) = &add;
  int (*pf2)(int, int) = add;
  //
  //&函数名和函数名都是函数的地址
  printf("%p\n", pf1);
  printf("%p\n", pf2);
  //函数指针的调用
  (*pf1)(2, 3);
  //(*pf1)解引用得到函数,然后括号传参,调用函数
  add(2, 3);//我们知道这种形式是一般调用函数的方式
  //int (*pf2)(int, int) = add;// add的地址给了pf2 说明pf2实际上就可以是add
  //add(2, 3)的时候也是add表示的是函数的地址
  //所以可以这样写
  pf2(2, 3);//得到结果一样
  //那么(*pf2)这个星号到底有没有用呢,答案是用不到的
  //(*******pf1)(2, 3);//写多少星号最后都是要得到add的地址,所以星号没有意义,但是只要加星号就要括号括起来,避免优先级问题
  return 0;
}

下面是《C陷阱和缺陷》中的两行代码

//代码1

(*(void (*)())0)();

//代码2

void (*signal(int , void(*)(int)))(int);

总结

以上是本文对于指针进阶的一系列指针相关内容的描述,接下来是下半部分对于指针进阶的内容的讲解,大家多多支持,您的每一次点赞、评论,都是我前进的动力!!!

相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
60 0
|
23天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
76 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
23天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
47 9
|
23天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
42 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
120 13
|
27天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
27天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
96 3
|
28天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
56 11
|
27天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
37 1