【C语言】指针总结,值得收藏哦

C语言指针一览表

在这里插入图片描述

一、指针的概念

1、内存与地址

随着科技的进步,人们的生活也在发生改变,电子产品也是!

我们常听说,某某的新买的电脑是64位的,某某的电脑是32位。这里说的“位”,实则是中央处理器(CPU)的字长,作用呢,就是 CPU一次能够处理二进制数的最大位数

现在主流电脑都是32位或者64位,这跟我们所说的内存有关系吗?

当然有! 举个栗子:

小明和小芳是高中同学,过几天呢,就是小芳的18岁生日,小芳就邀请小明去她家一起庆祝生日。小明心想“这感情好啊,直接去她家过生日!!!”,说:“小芳芳,你家在重庆哪儿呢?”

在这里插入图片描述

“咳咳,别走远了”

“在**滨江路250号**,你星期天过来吧!”,小芳说;

扫描二维码关注公众号,回复: 13151288 查看本文章

“不要想太多,重心不在其他事上!”

上文,提到了*滨江路250号*,这就是一个地址啊,小明可以通过这个地址找到小芳芳家。我们的电脑也是可以通过“地址”找到一块内存空间的。

如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHDzr1kz-1622041026118)(asserts/20210526200917.png)]

重点:

  • 32位电脑的内存地址范围是 0 ~ 232 - 1
  • 64位电脑的内存地址范围是0 ~ 264 -1
  • 每一个单位的内存空间的大小是 1个字节
  • 字长不同的电脑,指针所占空间的大小也不一样,32位电脑上,一个指针变量占 4个字节,64位电脑上,占 8个字节

2、一级(二级)指针的概念

指针?什么是指针?

从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。

int a = 20; //整形变量
int* p1 = &a; //一级指针
int** p2 = &p1; //二级指针

上述p1与p2就是两个指针变量,先看p1,p1变量的类型是 int*, int* 就代表它是一个一级指针。

再看p2,p2变量的类型是int**,有两颗星,就代表这是个二级指针,再看它的值是 &p1,是取的一级指针的内存地址。也就是说,1. 二级指针 指向的是一个 一级指针; **2.**一级指针指向的是基本数据类型,int、char、short等。

二、野指针的概念

1、概念

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

2、成因

a. 指针未初始化,如下

int main()
{
    
    
    int a = 10;
    int* p1 = &a; //正确,把a的内存地址赋值给p1
    int* p2;     //错误,未初始化,若直接进行访问,会报错
    return 0;
}

b. 指针越界访问,如下

int main()
{
    
    
    int arr[10] = {
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int* p = arr; //数组名,首元素地址
    for (int i = 0; i < 11; i++) 
    {
    
    
        printf("%d ",*(p+i)); //数组元素共10个,最后一个元素的下标是9,i变量能够等于10,越界
        printf("%d ",arr[i]); //此处两个printf函数的作用,等价
    }   
	return 0;
}

c.指针指向的内存空间已经被释放,如下

struct Student
{
    
    
    char name[20];
    int age;
};
int main()
{
    
    
    //创建一个结构体,返回的是指向 struct Student类型的指针变量 s1
    struct Student* s1 = (struct Student*)malloc(sizeof(struct Student)); 
    
    //假设现在我们不需要这块空间了,在C/C++中,malloc申请的内存空间需要程序员自己手动释放(回收)
    free(s1);
    
    //现在s1已经被释放(回收)了,意思是 由变量s1指向的这块内存空间还给了系统
    //但是s1里面还是保存着这块内存空间(struct Student)的内存地址(可理解为房间号)
    //如果此时再次去访问这块空间,那就会出错的。因为这块空间已经回收了
    s1->name = "博尔特";  //错误,内存空间已经回收,这也称为也指针
    return 0;
}

3、如何规避上诉问题?

有人说:“不使用指针,就完美的规避了”。 那不可能啊,衡量一个C/C++程序员的技术水平,指针的运用占了不小的份额。

  • 指针初始化
  • 小心指针越界访问
  • 指针指向的空间释放后,及时置为NULL
  • 指针使用之前检查有效性

三、六大基本指针类型

1、char*

char ch = 'A';
char* p = &ch; //将ch的内存地址赋值给p

2、short*

short a = 1314;
short* p = &a; //将a的内存地址赋值给p

3、int*

int a = 10;
int* p = &a; //将a的内存地址赋值给p

4、long*

long a = 100000;
long* p = &a; //将a的内存地址赋值给p

5、float*

float a = 3.0;
float p = &a; //将a的内存地址赋值给p

6、double*

double a = 5.0;
double* p = &a; //将a的内存地址赋值给p

四、指针运算

1、& 与 *

&: 取地址符,可以取出变量在内存中的地址。

示例: &number, 取出number变量的内存地址

**:**间接访问操作符,也叫解引用,不要与二元运算符()混淆,二者符号相同,但语法不同。

int main()
{
    
    
    int a = 20;
    int* p = &a; //取a的地址
    printf("%d\n",*p); //通过指针p,进行解引用,去输出a的值
    return 0;
}

2、指针 ± 整数

int main()
{
    
    
    char arr1[5] = {
    
    'a','b','c','d','e'};
    int arr2[5] = {
    
    1, 2, 3, 4, 5};
    char* p1 = arr1;
    int* p2 = arr2;
    for (int i = 0; i < 5; i++)
    {
    
    
        printf("%c ",*(arr1 + i));
    }
    for (int i = 4; i >= 0; i--)
    {
    
    
        printf("%d ", *(arr2 + i));
    }
    return 0;
}

上诉代码输出的结果是

a b c d e

5 4 3 2 1

char类型的变量占一个字节的空间,int类型的变量占四个字节的空间。而两个指针变量p1和p2也能够加减一个整数去读取内存中的数据。

也就是说,int* 的指针p2 + 1 ,并不是向后访问一个字节空间,而是访问的int类型的4个空间。例如:short* 的指针变量 p+1,向后访问的是short类型的2个空间,如下图int*类型指针加减的过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V4KIv7Zh-1622041026120)(asserts/%E5%8A%A8%E7%94%BB.gif)]

3、指针 - 指针

int my_strlen(char *s) {
    
    
       char *p = s;
       while(*p != '\0' )
              p++;
       return p-s; 
}
int main()
{
    
    
	int a = 10;
	int b = 20;
	int* p1 = &a;
	int* p2 = &b;
	printf("%d\n", p2 - p1);
	printf("%d\n", p1 - p2); 高地址 减 低地址 再+1,得到的是两块内存空间之间的内存空间个数
	return 0;
}

4、const int* p 与 int* const p

int a = 10;
const int* p = &a;
int* const p = &a;

第一个:const int* p = &a; const关键字是修饰的是 *p,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQqKgkrB-1622041026121)(asserts/%E5%8A%A8%E7%94%BB1-1622037903111.gif)]
在这里插入图片描述

第二个:int* const p = &a;这次const关键字修饰的 p,而不是*p。如图

在这里插入图片描述
在这里插入图片描述

综上所诉,谁写在const的后面就是修饰谁,谁就不能改动。

const int* p = &a; 此时 *p = 20; 不可取 ,* p被修饰

int* const p = &a; 此时 p = &b; 不可取,p被修饰

五、指针与数组

1、指针数组

中心意思是一个数组,这个数组里面的每一个元素是指针类型的变量

int a = 10;
int b = 20;
int* arr[2] = {
    
     &a, &b}; //这就是一个指针数组

2、数组指针

中心意思是一个指针,这个指针指向的是一个数组的空间。

//第一种情况
int arr[3][3] = {
    
    {
    
    1,2,3},{
    
    3,4,5},{
    
    5,6,7}};
int (*parr)[3] = arr; //此处的arr是二维数组的首元素地址,这个地址是{1,2,3}这个一维数组的地址

//第二种情况
int arr2[3] = {
    
    7,8,9};
int (*parr2)[3] = &arr;  //此处取出的是arr这整个数组的地址,注意区分二者

3、一维数组传参

#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);
}

4、二维数组传参

void test(int arr[3][5])//ok
{
    
    }
void test(int arr[][])//ok
{
    
    }
void test(int arr[][5])//ok
{
    
    }
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//不ok
{
    
    }
void test(int* arr[5])//不ok
{
    
    }
void test(int (*arr)[5])//ok
{
    
    }
void test(int **arr)//不ok
{
    
    }
int main()
{
    
    
 int arr[3][5] = {
    
    0};
 test(arr);
}

不管是一维数组传参还是二维数组传参,归根结底还是要弄清楚,所传递过去的参数是什么,特别需要区分数组,arr和&arr,一维还是二维。二者还是有一定的区别。

六、函数指针

先看一段代码。

#include <stdio.h>
void test()
{
    
    
 printf("hehe\n");
}
int main()
{
    
    
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0; }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4JiWw9Nc-1622041026124)(asserts/image-20210526224046709.png)]

两种printf函数输出的就是test函数的地址,通过这个地址可以去调用这个函数。

那如何保存呢??

void (*ptest)(void) = &test; //*先和ptest结合,说明是一个指针,往外看,后面是个(void)
							//说明是一个函数,这就是函数指针

回调函数

概念:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一

个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该

函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或

条件进行响应。

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2) {
    
    
  return (*( int *)p1 - *(int *) p2);
}
int main()
{
    
    
    int arr[] = {
    
     1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
   {
    
    
       printf( "%d ", arr[i]);
   }
    printf("\n");
    return 0; }

七、总结

指针的重要性,就不用多说了,在后面学习数据结构(链表,二叉树等等),如果不能够很好的运用指针,后面数据结构就会变得不容易。

多看一些视频,多做一些练习题,相信你能够更好的理解指针这一概念。

欢迎阅读,下期见咯!!!

猜你喜欢

转载自blog.csdn.net/x0919/article/details/117306885