C learning_14 指针篇之破解版

简介: C learning_14 指针篇之破解版

1. 指针是什么


       指针是C语言中一种非常重要的数据类型,它存储着一个变量的内存地址。通过指针,程序可以直接访问到对应内存地址上存储的数据,从而实现一些高效的操作。指针是内存中一个最小单元的编号,也就是地址。


写c语言程序的时候,创建的变量都要在内存上开辟空间,c语言通过&操作符取出变量的地址,平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。


总结:指针就是地址,口语中说的指针通常指的是指针变量(p指针指定就是指针变量)。


指针变量


通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。

#include <stdio.h>
int main()
{
    int a = 10;//在内存中开辟一块空间
    int* p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量\
    中,p就是一个指针变量。
    return 0;
}


总结:指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。


一个小的单元到底是多大?如何编址?


一个小的单元在计算机中通常指的是1个字节(Byte),也就是8个二进制位(bit)。一个字节是计算机存储数据的基本单位,可以用来存储字符、整数、布尔值等各种数据类型。


在计算机内存中,每个字节都有一个唯一的地址,用来标识该字节在内存中的位置。内存地址通常按照字节递增的顺序进行编址,比如第一个字节的地址为0x00000000,第二个字节的地址为0x00000001,以此类推。


假设我们有一个char类型的变量,我们可以使用指针来获取它在内存中的地址,如下所示:


```

char a = 'A';

char* p = &a;

printf("a的地址为:0x%x\n", p);  // 输出:a的地址为:0x7fff5fbff8c7

```


其中,&a表示获取a变量的地址,p是一个指向char类型变量的指针变量,输出的地址就是a变量在内存中的地址。需要注意的是,输出的地址的格式通常是16进制表示。


总结:


1.一个字节对应一个地址。

2.指针变量是用来存放地址的,地址是唯一标示一个内存单元的。

3.指针的大小在32位平台是4个字节,在64位平台是8个字节。


2. 指针和指针类型


指针类型是指指针变量指向的数据类型。指针变量所指向的数据类型不同,指针变量的类型也就不同。

在C语言中,指针类型的定义格式为:数据类型 *指针变量名;

char c = 'h';
int a = 10;
double pi = 3.14;
char  *pc = &c;
int   *pi = &a;
short *ps = &pi;


char* 类型的指针是为了存放 char 类型变量的地址;

int* 类型的指针是为了存放 int 类型变量的地址;

double* 类型的指针是为了存放 double 类型变量的地址。


那指针类型的意义是什么?



type* p;

 *说明p是一个指针变量。

1.p指向的对象的类型。

2.p解引用的时候访问的对象的大小是(sizeof(type))。

3.指针的类型决定了指针向前或者向后走一步有多大(距离)。


3. 野指针


概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

成因:

       1. 指针未初始化

       2. 指针越界访问

       3. 指针指向的空间释放

#include <stdio.h>
int main()
{
    int* p;//局部变量指针未初始化,默认为随机值
    *p = 20;//error C4700: 使用了未初始化的局部变量“p”
    return 0;
}


     这段代码中,定义了一个指向 int 类型数据的指针变量 p,但是并没有初始化它,这意味着 p 中存储的值是随机的,可能是任意值。


       在对指针 p 解引用赋值时,需要注意指针是否已经指向了一个可用的内存地址,否则会导致未定义的行为,可能会导致程序崩溃、数据损坏等问题。


       在这个程序中,指针 p 指向的内存地址是未知的,解引用赋值 *p = 20 会尝试将值 20 写入未知的内存地址,可能会导致程序崩溃或者其他严重的后果。


       因此,这个程序是不安全的,需要避免未初始化指针的使用。在定义指针变量后应该将其初始化为一个有效的内存地址,或者在使用指针之前检查其是否为 NULL。

#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    for (i = 0; i <= 11; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }
    return 0;
}


这段代码存在问题,主要存在以下两个问题:


1. 数组越界

       在程序中,定义了一个大小为10的数组arr,但是在循环中使用了 <=11 的循环条件,这将导致循环执行超出了数组arr的范围,从而导致数组越界。


2. 野指针

       在循环中,每次循环时使用指针p对数组arr进行赋值,但是在循环条件为 i = 10 时,指针p所指向的位置已经超出了数组arr的最大索引值,而在第11次循环时,指针p指向的地址已经不再是arr数组的地址了,因此会导致指针p成为野指针。这将导致对指针p的访问导致不可预期的结果。


       因此,为了避免这些问题,我们需要在循环中使用正确的条件来避免数组越界,同时还应该确保指针p的范围不超出数组arr的范围。

#include<stdio.h>
int* test()
{
  int a = 10;
  return &a;
}
int main()
{
  int* p = test();
  printf("%d\n", *p); //warning C4172 : 返回局部变量或临时变量的地址: a
  return 0;
}


这段代码存在一个问题,就是在函数test()中使用了一个局部变量a,并将其地址返回给了main()函数中的指针p。这是不安全的,因为在函数test()执行完毕后,局部变量a所占用的内存空间被释放,此时指向该内存空间的指针变成了野指针,使用该指针就会导致未定义行为。


规避野指针


1. 指针初始化,如若不知道初始化为何值,直接初始化为NULL

2. 小心指针越界

3. 指针指向空间释放,及时置NULL

4. 避免返回局部变量的地址

5. 指针使用之前检查有效性

#include <stdio.h>
int main()
{
    int a = 10;
    int * p = &a;
    int* ptr = NULL;//ptr是一个空指针,没有指向任何有效的空间.这个指针不能直接使用
    //int * ptr;野指针
    if (ptr != NULL)
    {
        //使用
    }
    return 0;
}


4. 指针运算


#include<stdio.h>
/*
  int arr[10]
  int* p = arr;
  arr是数组名,数组名就是首元素的地址
  p == arr == &arr[0]
  arr[i] == *(p+i) == *(arr+i)
  由于加法具有交换律
  *(arr+i) == *(i+arr) == i[arr]
*/
int main()
{
  int arr[10] = { 0 };
  //不使用下标访问数组
  int* p = &arr[0];
  int i = 0;
  int size = sizeof(arr) / sizeof(arr[0]);
  for (i = 0; i < size; i++)
  {
    //p指针位置改变
    *p = i;//指针p每次向后条sizeof(int)个字节
    p++;//p += 1;
    //p指针位置不变
    //*(p + i) = i;
  }
  p = &arr[0];
  for (i = 0; i < size; i++)
  {
    //printf("%d ", *(p+i));
    printf("%d ", i[arr]);//0 1 2 3 4 5 6 7 8 9
  }
  return 0;
}


指针+-整数



 这段代码定义了一个大小为N_VALUES的float类型数组values和一个指向float类型的指针vp。然后使用指针vp和数组的方式分别给数组中的所有元素赋值为0,最后指针vp指向数组中最后一个元素的后一个位置。具体解释如下:


1. 定义数组和指针:

```

       float values[N_VALUES];

       float *vp;

```

       这行代码定义了一个大小为N_VALUES的float类型数组values和一个指向float类型的指针vp,可以用指针vp访问数组中的元素。


2. 给数组中所有元素赋值为0:

```

for (vp = &values[0]; vp < &values[N_VALUES]; ) {

   *vp++ = 0;

}

```

       这段代码使用指针vp和数组的方式分别访问数组中的所有元素,将它们的值赋为0。其中,指针vp的初始值为数组的第一个元素的地址(&values[0]),每次循环使用指针解引用操作(*)访问当前指向的元素,并将其赋值为0,然后将指针vp自增一个元素大小的偏移量(float类型为4个字节),以便访问下一个元素。循环终止时,数组中的所有元素都被赋值为0。


3. 指针vp指向数组最后一个元素的后一个位置:

```

vp = &values[N_VALUES];

```

       这行代码将指针vp指向数组中最后一个元素的后一个位置。由于数组下标从0开始,而数组中共有N_VALUES个元素,所以最后一个元素的下标为N_VALUES-1。


指针-指针



这段代码创建了一个大小为10的int类型数组arr,并将其所有元素初始化为0。然后使用“指针-指针”的方式计算数组中最后一个元素地址与第一个元素地址之间的元素个数,结果为9(前提:两个指针指向了同一块空间)。具体解释如下:


1. 创建数组并初始化为0:

```

int arr[10] = { 0 };

```

       这行代码创建了一个大小为10的int类型数组arr,并将其所有元素初始化为0。


2. 使用“指针-指针”的方式计算数组中元素的个数:


```

printf("%d\n", &arr[9] - &arr[0]);

```

       这行代码使用“指针-指针”的方式计算数组中最后一个元素地址与第一个元素地址之间的元素个数。其中,&arr[9]表示数组中最后一个元素的地址,&arr[0]表示数组中第一个元素的地址,二者相减即为这两个地址之间的偏移量,即数组中元素的个数的绝对值。由于数组中共有10个元素,最后一个元素的下标为9,所以输出结果为9。

#include<stdio.h>
//计数器count
int my_strlen1(char* s)
{
  int count = 0;
  while (*s != '\0')
  {
    count++;
    s++;
  }
  return count;
}
//递归
int my_strlen2(char* s)
{
  if (*s == '\0')
    return 0;
  else
    return 1 + my_strlen2(s + 1);
}
//指针 - 指针
int my_strlen3(char* s)
{
  char* start = s;
  while (*s)
  {
    s++;
  }
  return s - start;
}
//指针 - 指针
int my_strlen4(char* s)
{
  char* start = s;
  while (*s++);
  return s - start - 1;
}
int main()
{
  char arr[] = "abcdef";
  //不算'\0'
  printf("%d\n", my_strlen1(arr));
  printf("%d\n", my_strlen2(arr));
  printf("%d\n", my_strlen3(arr));
  printf("%d\n", my_strlen4(arr));
  return 0;
}


指针的关系运算(比较指针的大小)



5. 指针和数组


 指针指的是指针变量,不是数组,指针变量的大小是4/8个字节,是专门来存放地址的。


       数组也不是指针,数组是一块连续的空间,存放一组相同类型的数据的。


       联系:      


       1. 数组名本身就是指向数组首元素地址的指针。例如,对于一个int类型的数组a,a就是指向a[0]的指针。

       2. 数组元素可以使用指针来进行访问。例如,a[i]可以写成*(a+i),其中a+i表示a的首地址加上i个元素的偏移量,也就是第i个元素的地址。

       3. 数组名也可以用来表示指向数组的指针,可以在某些情况下将数组名用作指针使用。例如,在函数参数中使用数组名时,实际上是传递了数组首元素的指针。

       4. 指针也可以用来模拟数组的行为。例如,可以用指针来动态分配内存来模拟变长数组。


总结:数组中,数组名其实就是数组首元素的地址,数组 == 地址 == 指针


       当我们知道数组首元素的地址的时候,因为数组又是连续存放的,所以通过指针就可以遍历访问数组,数组可以通过指针来访问。

#include <stdio.h>
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    int i = 0;
    //数组名就是首元素地址
    //arr == &arr[0]
    int* p = arr;
    for (i = 0; i < 10; i++)
    {
        //&arr[i] == p+i
        //通过指针访问
        printf("&arr[%d] = %p <=====>  %p\n",*(p+i), &arr[i],p+i);
        //所以 p + i 其实计算的是数组 arr 下标为i的地址。
    }
    return 0;
}


数组名表示的是数组首元素的地址。(2种情况除外)

       1.sizeof(数组名) 2.&数组名   -----这两个代表整个数组


6. 二级指针


       指针是一种特殊的变量,它的值为内存中另一个变量的地址。在 C 中,指针变量存储的是另一个变量的地址。如果我们要通过指针修改这个变量的值,我们可以使用一级指针。

但有时候我们需要通过指针修改指针所指向的变量的值,这时候就需要使用二级指针。简单来说,二级指针是指一个指针变量的地址,也就是指向指针的指针。


例如:

int a = 10;

int * p = &a;

int ** pp = &p;


这里,变量 `p` 是一个指向 `int` 类型的指针,它存储变量 `a` 的地址。而变量 `pp` 是一个指向 `int*` 类型的指针,它存储变量 `p` 的地址。这样我们就可以通过 `pp` 来修改 `a` 的值了。


例如:


**pp = 20;


这里,可以理解为 *(*pp)= 20,首先*pp通过解引用获取指针变量p的地址,然后再*p解引用修改a的值。


       使用二级指针时需要注意,要先分配好一级指针指向的内存空间,然后再通过二级指针来修改它所指向的变量的值。



7. 指针数组


       指针数组是数组,不是指针。它是由多个指针变量组成的数组,数组中的每个元素都是指针变量,它们的值是一个地址。我们可以通过指针数组来保存多个变量的地址,并且可以通过下标来访问这些变量。


指针数组的定义方式如下:

int *ptrArr[10]; //定义一个包含10个指向int类型的指针变量的数组


       在这个示例中,`ptrArr` 是一个包含10个指向 `int` 类型的指针变量的数组。我们可以使用下标来访问数组中的每个元素,例如 `ptrArr[0]`、`ptrArr[1]` 等,它们都是指向 `int` 类型的指针变量。


       指针数组可以用于保存多个变量的地址。

#include<stdio.h>
int main()
{
  char arr1[] = "abc";
  char arr2[] = "def";
  char arr3[] = "ghi";
  //指针数组,每一元素都是一个一维数组的地址
  char* ptr[] = { arr1,arr2,arr3 };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%s\n", ptr[i]);
  }
  return 0;
}

#include<stdio.h>
int main()
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
  //指针数组,每一元素都是一个一维数组的地址
  int* ptr[] = { arr1,arr2,arr3 };
  int i = 0;
  //打印数组的内容
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for(j=0;j<5;j++)
    {
      printf("%d ", ptr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

相关文章
|
人工智能 数据挖掘 定位技术
你等的GPT4看图功能,Bing早就有了,还免费
你等的GPT4看图功能,Bing早就有了,还免费
149 0
|
机器学习/深度学习 存储 Shell
分享使用谷歌Colab 常用小tips, 玩转Google Colab
分享使用谷歌Colab 常用小tips, 玩转Google Colab
分享使用谷歌Colab 常用小tips, 玩转Google Colab
|
6月前
|
算法 数据挖掘
Sentieon | 每周文献-Clinical Trial-第二十二期
Sentieon | 每周文献-Clinical Trial-第二十二期
42 2
|
存储 编解码 JavaScript
Google Earth Engine(GEE)——GEE最全介绍(7000字长文)初学者福音!
Google Earth Engine(GEE)——GEE最全介绍(7000字长文)初学者福音!
1918 0
Google Earth Engine(GEE)——GEE最全介绍(7000字长文)初学者福音!
|
机器学习/深度学习 资源调度 并行计算
李宏毅2021春季机器学习课程视频笔记1:Introduction, Colab & PyTorch Tutorials, HW1
李宏毅2021春季机器学习课程视频笔记1:Introduction, Colab & PyTorch Tutorials, HW1
李宏毅2021春季机器学习课程视频笔记1:Introduction, Colab & PyTorch Tutorials, HW1
|
机器学习/深度学习 人工智能 算法
吴恩达《Machine Learning》Jupyter Notebook 版笔记发布!图解、公式、习题都有了
吴恩达《Machine Learning》Jupyter Notebook 版笔记发布!图解、公式、习题都有了
1370 0
吴恩达《Machine Learning》Jupyter Notebook 版笔记发布!图解、公式、习题都有了
|
机器学习/深度学习 存储 人工智能
再也不怕复现论文!arXiv携手Papers with Code,提交论文+上传代码一步到位
昨日,Papers with Code宣布,arXiv网站将允许研究人员在提交论文的同时提交代码,让所有感兴趣的人可以轻松地分析、审查或者复制最先进的人工智能技术及其取得的新进展。
626 0
再也不怕复现论文!arXiv携手Papers with Code,提交论文+上传代码一步到位
撒花!吴恩达《Machine Learning Yearning》完结!
撒花!吴恩达《Machine Learning Yearning》完结!
111 0
撒花!吴恩达《Machine Learning Yearning》完结!
|
机器学习/深度学习 人工智能 算法
重磅 | 吴恩达新书《Machine Learning Yearning》最新版分享
重磅 | 吴恩达新书《Machine Learning Yearning》最新版分享
544 0
重磅 | 吴恩达新书《Machine Learning Yearning》最新版分享
|
并行计算 openCL PyTorch