深度剖析指针(上)——“C”

简介: 深度剖析指针(上)——“C”

各位CSDN的uu们你们好呀,今天,小雅兰的内容是指针噢,在学习C语言的过程中,指针算是一个比较重要的内容,当然,难度也是比较大的,那么现在就让小雅兰来带大家进入指针的世界吧


字符指针


数组指针


指针数组


数组传参和指针传参


之前其实已经写过指针的博客了,但是写得不是很深入


指针——“C”_认真学习的小雅兰.的博客-CSDN博客


指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

指针的大小是固定的4/8个字节(32位平台/64位平台)。

指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

指针的运算。

接下来,让我们详细剖析一下指针


字符指针


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

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
  char ch = 'w';
  char* pc = &ch;//pc就是字符指针
    *pc = 'w';
  return 0;
}

char * p="abcdef";//是把字符串首字符的地址存放在p中


//表达式的值是首字符的地址,右边是一个常量字符串,常量字符串不能修改


所以,一般会在char * p前面加上一个const,使得*p的内容不允许被修改


const char * p="abcdef";


const的作用之前小雅兰也详细写过


实用调试技巧——“C”_认真学习的小雅兰.的博客-CSDN博客


char arr[]="abcdef";


char * p=arr;//p指向的是数组的首元素,arr数组是可以修改的

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

代码 const char* pstr = "hello bit.";特别容易让人误以为是把字符串 hello bit 放到字符指针 pstr 里了,但是本质是把字符串 hello bit. 首字符的地址放到了pstr中。

0192f8f07e104987a83f18349ed5be14.png

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

下面,我们来看一道题目:

这道题目出自于《剑指offer》

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    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;
}

05b9593a152a403ba7e7dcb2b6669291.png

str1和str2 是两个数组,数组名表示首元素的地址,但是这两个数组不是同一块空间。


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


所以str1和str2不同,str3和str4不同。


但是,&str3和&str4就是完全不一样的啦


指针数组


采用类比法的方法,来理解就可以了。

  • 字符数组——存放字符的数组——char arr1[10];
  • 整型数组——存放整型的数组——int arr2[5];
  • 指针数组——存放的就是指针
  • 存放字符指针的数组——字符指针数组——char * arr3[5];
  • 存放整型指针的数组——整型指针数组——int * arr4[6];
#include<stdio.h>
int main()
{
  char* arr[] = { "abcdef","hehe","qwer" };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%s\n", arr[i]);
  }
  return 0;
}

字符指针数组!!!

a10783c3d5734c87bcf32de98c52e008.png

c7ae263748f34c4b9cd9aa794d529797.png

下面,我们来利用整型指针数组,来模拟实现一个二维数组,其实之前也写过,现在再来复习一下!!!

#include<stdio.h>
int main()
{
  int arr1[5] = { 1,2,3,4,5 };
  int arr2[5] = { 6,7,8,9,10 };
  int arr3[5] = { 11,12,13,14,15 };
  //arr是一个存放整型指针的数组
  int* arr[] = {arr1,arr2,arr3};
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%-3d ", arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

694b24ff1be342b58e55cd67dcc8d96f.png 这个代码还有另外一种写法:

#include<stdio.h>
int main()
{
  int arr1[5] = { 1,2,3,4,5 };
  int arr2[5] = { 6,7,8,9,10 };
  int arr3[5] = { 11,12,13,14,15 };
  //arr是一个存放整型指针的数组
  int* arr[] = { arr1,arr2,arr3 };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%-3d ", *(arr[i]+j));
    }
    printf("\n");
  }
  return 0;
}

e306789d8837405381298dac4b9e5ff1.png

数组指针


依旧还是采用类比法!!!


整型指针——指向整型的指针


int a=10;


int * p=&a;


字符指针——指向字符的指针


char ch='w';


char * pc=&ch;


数组指针——指向数组的指针


int arr[10];


int(*pa)[10]=&arr;//取出的是数组的地址


char arr[10];


char(*pc)[10]=&arr;


int * arr[5];


int * (*p)[5]=&arr;


指针数组——是数组——是一种存放指针的数组


数组指针——是指针——是一种指向数组的指针


int (*p)[10];


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


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


&数组名VS数组名


小雅兰其实之前也写过这些内容,只是涉及不深


数组——“C”_认真学习的小雅兰.的博客-CSDN博客


对于下面的数组:


int arr[10];


arr 和 &arr 分别是啥?


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


那&arr数组名到底是啥?


我们看一段代码:  

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

d41437c6555644c6889e3037698a2b27.png

数组名绝大部分情况下是数组首元素的地址


但是有两个例外


1.sizeof(数组名)——sizeof内部单独放一个数组名的时候,数组名表示整个数组,计算得到的是数组的总大小


2.&arr——这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样


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


难道两个是一样的吗?


我们再看一段代码:

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

7d0913461bb84c279f00afdb58e95d82.png

e45e82c5582543559ae63446cd1a16f7.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,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  //下标的形式访问数组
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
    return 0;
}
#include<stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
    //指针来访问
  int* p = arr;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(p + i));
  }
    return 0;
}
#include<stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
    //数组指针
  //一些别扭的写法,虽然对,但是不推荐
  //不要强行去用数组指针
  int(*p)[10] = &arr;
  int i = 0;
  //p——&arr
  //*p——*&arr
  //*p——arr
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *((*p) + i));
  }
  for (i = 0; i < sz; i++)
  {
    printf("%d ", (*p)[i]);
  }
  return 0;
}

一维数组指针的使用:

#include<stdio.h>
//一维数组传参,形参是数组
void print(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
//一维数组传参,形参是指针
void print(int * arr, int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);//与arr[i]一样
  }
}
void print(int* arr, int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(arr+i));
  }
}
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  print(arr, sz);
  return 0;
}

二维数组指针的使用:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      printf("%-3d ", arr[i][j]);
    }
    printf("\n");
  }
}
void print_arr2(int(*arr)[5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      printf("%-3d ", arr[i][j]);
    }
    printf("\n");
  }
}
void print_arr2(int(*arr)[5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      printf("%-3d ", *(*(arr+i)+j));//与arr[i][j]一样
    }
    printf("\n");
  }
}
int main()
{
  int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
  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是数组,数组中存放的是指针,该指针指向的又是数组

数组参数、指针参数

一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int* arr)//ok
{}
void test2(int* arr[20])//ok
{}
void test2(int** arr)//ok
{}
int main()
{
  int arr[10] = { 0 };//整型数组
  int* arr2[20] = { 0 };//指针数组
  test(arr);
  test2(arr2);//数组的每个元素都是int* 类型
  //一维数组传参,形参可以是数组,也可以是指针
  //当形参是指针的时候,要注意类型
}

二维数组传参

#include<stdio.h>
void test(int arr[3][5])//ok
{}
void test(int arr[][])//error
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//error
//不可以用一个整型指针来接收一行的地址
{}
void test(int* arr[5])//error
//指针数组
{}
void test(int(*arr)[5])//ok
//数组指针
{}
void test(int** arr)//error
//二级指针
//接收一级指针的地址,传过去的压根不是一个一级指针
{}
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;
}

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

整型变量的地址、整型指针、数组首元素的地址

189a7e9ff75b434aa2c9b73ff0c0bfd2.png

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
  printf("num = %d\n", **ptr);
}
int main()
{
  int n = 10;
  int* p = &n;//p是一级指针
  int** pp = &p;//pp是二级指针
  test(pp);
  test(&p);
  return 0;
}

思考:当函数的参数为二级指针的时候,可以接收什么参数?

二级指针变量、一级指针变量的地址、指针数组

void test(char** p)
{
}
int main()
{
  char c = 'b';
  char* pc = &c;
  char** ppc = &pc;
  char* arr[10];
  test(&pc);
  test(ppc);
  test(arr);//Ok
  return 0;
}

好啦,小雅兰今天的内容就到这里啦,指针这个大小,总体来说难度还是很大的,所以,小雅兰也要花很多时间去消化呀,那么,函数指针和回调函数的知识点敬请期待小雅兰的下一篇博客噢!!!小雅兰加油呀!!!

6ba147dcdd6d438ba098f99d36d6b054.jpg

相关文章
|
7月前
|
存储 算法 C语言
C语言深度剖析指针
C语言深度剖析指针
50 0
|
9月前
|
存储 C语言 C++
深度剖析C语言指针笔试题 Ⅱ
&lt;1&gt;主页:C语言的前男友 &lt;2&gt;知识讲解:C语言指针 &lt;3&gt;创作者:C语言的前男友 &lt;4&gt;开发环境:VS2022 &lt;5&gt;前言:继续练习指针的笔试题,今天的笔试题更有难度哦。欢迎大家前来指正,如果觉得作者写的还不错的话,请麻烦动动发财的小手,关注,点赞,收藏,评论。
|
10月前
|
存储
学C的第二十三天【继续深度剖析数据在内存中的存储:3. 浮点型在内存中的存储(重点);练习:1. 有序序列判断;2. 获得月份天数(多组输入);3. 使用指针打印数组内容;4. 使用指针使字符串逆序】-2
(4). 取出内存中的 指数E(三种情况):E全为1 指数E 是通过 真实值+中间值 算出来的,如果E全是1,(32位系统)说明E的真实值是 128,指数是128说明这个值是非常大的。 这时,如果 有效数字M 全为0,表示 ±无穷大(正负取决于符号位s)
深度剖析指针(下)——“C”
深度剖析指针(下)——“C”
|
C语言 索引
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表2
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
82 0
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表2
|
机器学习/深度学习 存储 索引
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
97 0
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
|
存储 C语言
⭐️ 关键字深度剖析 ⭐️第五章(深入C语言三种类型(float/bool/指针)与“零值“的比较)(续)
浮点数在内存中存储,并不想我们想的是完整存储的 在十进制转化成为二进制,是有可能有精度损失的
⭐️ 关键字深度剖析 ⭐️第五章(深入C语言三种类型(float/bool/指针)与“零值“的比较)(续)

热门文章

最新文章