【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);

总结

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

相关文章
|
5天前
|
C语言
c语言指针总结
c语言指针总结
15 1
|
3天前
|
存储 安全 编译器
C语言详解指针(指针海洋的探索,将传值与传址刻在心里)
C语言详解指针(指针海洋的探索,将传值与传址刻在心里)
8 0
|
5天前
|
C语言
C语言(指针详解)重点笔记:指针易错点,都是精华
C语言(指针详解)重点笔记:指针易错点,都是精华
12 0
|
5天前
|
存储 C语言
C语言指针讲解(适用于初学者)
C语言指针讲解(适用于初学者)
6 0
|
5天前
|
搜索推荐 C语言
详解指针进阶2
详解指针进阶2
|
5天前
|
存储 程序员 C语言
【C 言专栏】C 语言指针的深度解析
【4月更文挑战第30天】C 语言中的指针是程序设计的关键,它如同一把钥匙,提供直接内存操作的途径。指针是存储其他变量地址的变量,通过声明如`int *ptr`来使用。它们在动态内存分配、函数参数传递及数组操作中发挥重要作用。然而,误用指针可能导致错误,如空指针引用和内存泄漏。理解指针的运算、与数组和函数的关系,以及在结构体中的应用,是成为熟练 C 语言程序员的必经之路。虽然挑战重重,但掌握指针将增强编程效率和灵活性。不断实践和学习,我们将驾驭指针,探索更广阔的编程世界。
|
5天前
|
存储 C语言
C语言进阶---------作业复习
C语言进阶---------作业复习
|
5天前
|
存储 Linux C语言
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
5天前
|
自然语言处理 Linux 编译器
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)