指针进阶(纯干货!!!)

简介: 指针进阶(纯干货!!!)

前言


上期我们详细讲解了初阶指针,相信大家应该都有所收获吧,本期给大家带来指针的高阶用法,让大家对指针的了解更加深入。


字符指针


在指针的类型中我们知道有一种指针类型为字符指针 char* ;

一般使用方式:


int main()
{
  char ch = 'w';
  char *pc = &ch;
  *pc = 'w';
  return 0;
}


另一种使用方式:


int main()
{
  const char* pstr = "hello hello.";
  //这里是把一个字符串放到pstr指针变量里了吗?
  printf("%s\n", pstr);
  return 0;
}

输出:hello hello.


其实不是字符串 hello hello. 放到字符指针 pstr 里了,本质是把字符串首字符的地址放到了pstr中。


我们来看下面代码:


#include <stdio.h>
int main()
{               //输出结果会是什么呢?
  char str1[] = "hello hello.";
  char str2[] = "hello hello.";
  const char *str3 = "hello hello.";
  const char *str4 = "hello hello.";
  if(str1 ==str2)
  printf("str1 and str2 are same\n");
  else
  printf("str1 and str2 are not same\n");
  if(str3 ==str4)
  printf("str3 and str4 are same\n");
  else
  printf("str3 and str4 are not same\n");
  return 0;
}

输出:


fc4a08955b8f430c85ae3b6f044ba21e.png


这里str3和str4指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。


指针数组


我们来复习一下上期的数组指针


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


[ ] 的优先级大于 * ,所以arr先与 [ ] 结合成为数组,再与 * 结合,这样数组的每个元素的类型为 int(类型)*


数组指针


数组指针的定义


先来思考一下:数组指针是指针?还是数组?


当然是指针啦,数组相当于修饰,其本质是指针。


我们知道:

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。


那数组指针应该是:能够指向数组的指针。


我们来判断一下,下面代码哪个是数组指针?


int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?


答案:p1是指针数组;p2是数组指针。

为什么呢?


p2先和*结合,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。

(注意:[ ] 的优先级要高于 * 号的,所以必须加上()来保证p先和 * 结合。)


&数组名VS数组名


对于下面的数组:


int arr[10];


arr 和 &arr 分别是啥?

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

那&arr数组名到底是啥?

我们看一段代码:


#include <stdio.h>
int main()
{
  int arr[10] = {0};
  printf("%p\n", arr);
  printf("%p\n", &arr);
  return 0;
}


输出:


e64b55dad53642c7910d004e127a8d01.png


可见数组名和&数组名打印的地址是一样的。

难道两个是一样的吗?

我们再看一段代码:


#include <stdio.h>
int main()
{
  int arr[10] = { 0 };
  printf("arr = %p\n", arr);
  printf("&arr= %p\n", &arr);
  printf("arr+1 = %p\n", arr+1);
  printf("&arr+1= %p\n", &arr+1);
  return 0;
}


2815098df5bd4b9689f29080d8cbe398.png


根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。


实际上: &arr 表示的是整个数组的地址,而不是数组首元素的地址。

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型。

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40


数组指针的使用


那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

看代码:


#include <stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,0};
  int (*p)[10] = &arr;  //把数组arr的地址赋值给数组指针变量p
                      //但是我们一般很少这样写代码
  return 0;
}


一个数组指针的使用:


#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++)
  {
  for (j = 0; j < col; j++)
  {
    printf("%d ", arr[i][j]);
  }
  printf("\n");
  }
}
void print_arr2(int(*arr)[5], int row, int col)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++)
  {
  for (j = 0; j < col; j++)
  {
    printf("%d ", arr[i][j]);
    //printf("%d",*(arr[i]+j));
    //printf("%d",*(*(arr+i)+j));
    //因为arr表示第一行的一维数组,所以arr[i]表示第i行的一维数组
    //而arr[i]=*(arr+i),所以三个打印效果一样
  }
  printf("\n");
  }
}
int main()
{
  int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
  print_arr1(arr, 3, 5);
  //数组名arr,表示首元素的地址
  //但是二维数组的首元素是二维数组的第一行
  //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
  //可以数组指针来接收
  print_arr2(arr, 3, 5);
  return 0;
}


学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:


int arr[5];           //数组
int *parr1[10];       //指针数组
int (*parr2)[10];     //数组指针
int (*parr3[10])[5];  //数组指针数组(数组parr3[10]里存了int(*)[5]类型的指针,也就是存了数组指针)


数组传参和指针传参


在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?


一维数组传参


#include <stdio.h>
void test(int arr[])    //ok?
{}             //定义一个数组接收没问题OK
void test(int arr[10])  //ok?
{}             //定义一个数组接受没问题OK
void test(int* arr)     //ok?
{}             //传递过来的arr就是地址,定义一个指针接收没问题OK
void test2(int* arr[20])//ok?
{}            //定义一个指针数组接收没问题OK
void test2(int** arr)   //ok?
{}            //因为指针数组里存的是指针,所以用二级指针接收没问题OK
int main()
{
  int arr[10] = { 0 };
  int* arr2[20] = { 0 };    //指针数组
  test(arr);
  test2(arr2);
}


总结 : 形参可以是数组 , 也可以是指针.


二维数组传参

void test(int arr[3][5])//ok?
{}                  //OK
void test(int arr[][])//ok?
{}                  //不行,未定义数组每行有多少元素
void test(int arr[][5])//ok?
{}                  //OK
void test(int *arr)//ok?
{}                  //arr是第一行的一维数组地址,类型不匹配,不行
void test(int* arr[5])//ok?
{}                  //传递的arr表示一维数组地址,不能用数组接收,不行
void test(int (*arr)[5])//ok?
{}                  //指针arr指向5个元素,每个元素int型,OK
void test(int **arr)//ok?
{}                  //传递的是一维数组地址,不能用二级指针接收,不行
int main()          //二级指针是用来接收一级指针变量的地址
{
  int arr[3][5] = {0};
  test(arr);
}


总结:二维数组传参,函数形参的设计只能省略第一个 [ ] 的数字。

因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

这样才方便运算。


一级指针传参


#include <stdio.h>
void print(int *p, int sz)
{
  int i = 0;
  for(i=0; i<sz; i++)
  {
  printf("%d\n", *(p+i));
  }
}
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9};
  int *p = arr;
  int sz = sizeof(arr)/sizeof(arr[0]);
  //一级指针p,传给函数
  print(p, sz);
  return 0;
}


思考:


当一个函数的参数部分为一级指针的时候,函数能接收什么参数?


void test(int *p)
{}
int a=0;
test(&a); //ok?
                //传递地址ok
int *ptr=&a;
test(ptr); //ok?
               //传递指针变量ok
int arr[10];
test(arr); //ok?        //ok


二级指针传参


#include <stdio.h>
void test(int** ptr)
{}
int main()
{
  int n = 10;
  int*p = &n;
  int **pp = &p;
  //1.test(pp);
  //2.test(&p);
  int* arr[10];
  //3.test(arr);
  return 0;
}


以上三种传参方式都行.


最后


关于指针,我们已经学的差不多了,还差函数指针,回调函数,我们下次继续学习.

下期见~


相关文章
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
6月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
6月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
6月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
6月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
6月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
50 4
|
6月前
指针进阶(3)
指针进阶(3)
43 1
|
6月前
|
C++
指针进阶(1)
指针进阶(1)
46 1
|
6月前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
50 2
|
6月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
49 0