C learning_14 指针篇之破解版

news2025/1/18 6:49:16

目录

1. 指针是什么

指针变量

2. 指针和指针类型

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

3. 野指针

规避野指针

4. 指针运算

指针+-整数

指针-指针

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

5. 指针和数组

6. 二级指针

7. 指针数组


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/540286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年美国大学生数学建模竞赛E题光污染解题全过程文档及程序

2023年美国大学生数学建模竞赛 E题 光污染 原题再现&#xff1a; 背景   光污染被用来描述任何过度或不良地使用人造光。我们所说的一些光污染现象包括光侵入、过度照明和光杂波。在大城市&#xff0c;这些现象最容易被观测到的是太阳落山后天空中的一道辉光; 然而&#xf…

网课录屏怎么录?分享3个录制网课方法!

案例&#xff1a;怎样才能录制清晰流畅的网课视频&#xff1f; 【我最近在网上购买了一个配音的课程&#xff0c;每天晚上8点老师会进行讲课&#xff0c;但是课程没有回放。我想通过录屏的方式将课程内容录制下来&#xff0c;那怎样才能录制清晰流畅的网课视频呢&#xff1f;】…

初识Mybatis -- Mybatis入门保姆级教程(一)

文章目录 前言一、认识mybatis1.mybatis是什么2.jdbc缺点3.mybatis优点4.MyBatis框架解决JDBC劣势 二、mybatis入门案例1.需求分析与操作步骤2.创建表tb_user、实体类和Mapper接口3.创建maven模块re_mb_demon4.在pom.xml配置文件中导入相关依赖5. 配置相关文件6.创建并编写模拟…

百汇BCR:十个外汇交易常见问题解析

外汇交易是一种受到市场欢迎的投资方式&#xff0c;参与交易很容易&#xff0c;但想要盈利却很难。特别是一些细节问题容易被忽略&#xff0c;百汇BCR小编整理了十个外汇交易常见问题供有需要的投资者借鉴学习。 问题一、外汇开户需要哪些资料&#xff1f; 个人相关信息资料、…

重新理解RocketMQ Commit Log存储协议

最近突然感觉&#xff1a;很多软件、硬件在设计上是有root reason的&#xff0c;不是by desgin如此&#xff0c;而是解决了那时、那个场景的那个需求。一旦了解后&#xff0c;就会感觉在和设计者对话&#xff0c;了解他们的思路&#xff0c;学习他们的方法&#xff0c;思维同屏…

JSON格式化工具

格式化JSON有多种方式&#xff0c;选两种。 1.在线JSON格式化 我喜欢用这个网站&#xff1a;在线JSON工具 2.Notepad插件 需要插件 - JSON Viewer。 安装方式&#xff1a; 2.1 点击工具栏“插件”-选择“插件管理…”,弹出插件管理窗口&#xff0c;在“可用”tab页&#xf…

一般人不会告诉你的FP独立站低成本运营内幕

不少朋友听说FP独立站盈利比较高之后就兴致勃勃地跟着别人建站、装修、选品……但在此之前有没有仔细想过&#xff0c;一个成功的F牌独立站到底要怎么运营&#xff1f;而且是&#xff0c;怎么低成本运营&#xff1f;现在我就跟大家分享一下&#xff0c;希望有想法入局F牌独立站…

CorelDRAW2023最新v24.4.0.623中文稳定版

图形设计软件CorelDRAW2023最新版下载矢量图形制作工具,CorelDRAW&#xff08;简称CDR&#xff09;是一款专业的图形设计软件。该软件是Corel公司开发的一款功能强大的专业平面设计软件、矢量设计软件、矢量绘图软件。这款矢量图形制作工具软件广泛应用于商标设计、标志制作、封…

史上最全类和对象 ,只要你认真看完C++类和对象,分分钟钟都吊打面试官【 C++】

文章目录 基础篇面向过程和面向对象类的引入类的定义类的访问限定符类的封装类的作用域类的实例化类对象模型如何计算类对象的大小类对象的存储方式猜测对象中包含类的各个成员代码只保存一份&#xff0c;在对象中保存存放代码的地址只保存成员变量&#xff0c;成员函数存放在公…

10.计算机基础-操作系统面试题—计算机系统概述、进程管理

本文目录如下&#xff1a; 计算机基础-操作系统 面试题一、计算机系统概述CPU 内核态 和 用户态 的区别&#xff1f;从 用户态 切换到 内核态 的常见方法&#xff1a; 二、进程管理线程 和 进程 的区别&#xff1f;协程 (goroutine) 和 线程的区别?进程有哪些状态&#xff1f;…

如果一定要在C++和JAVA中选择,是C++还是java?

前言 C和Java都是广泛应用于软件开发领域的高级编程语言。它们都有着各自的优势和适用场景&#xff0c;因此在进行选择时需要考虑到具体的需求和使用情况。 首先&#xff0c;C是一种面向对象的编程语言&#xff0c;它允许程序员直接控制计算机硬件&#xff0c;而且拥有较低的…

【分享】免费并集多个人工智能于一体的在线使用网站

哈喽&#xff0c;大家好&#xff0c;我是木易巷~ 今天来给大家分享一个集ChatGPT、GPT4、Claude等人工智能于一体的在线使用网站——Poe。 地址&#xff1a;https://poe.com 以下是一个汇集了目前热门人工智能工具的网站&#xff0c;只需使用一个神奇的工具&#xff0c;通过邮…

Todoist 的 10 种最佳替代品(功能、优点、缺点、定价)

寻找最好的 Todoist 替代品&#xff1f; Todoist对项目管理者来说并不陌生&#xff0c;它自 2007 年以来就一直很出名&#xff01; ……但是作为老牌的组织类应用程序之一&#xff0c;现在再用它是否会觉得有点不顺手&#xff1f; 虽然它为了更好适应当今的工作节奏&#xf…

java 餐饮培训平台系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 餐饮培训平台系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

Redis学习---05

一、Redis集群搭建&#xff0c;Redis主从复制&#xff0c;读写分离 默认情况下每台redis服务器都是主节点。 (1) 主从复制&#xff1a;是指将一台redis服务器的数据&#xff0c;复制道其他redis服务。前者成为主节点&#xff0c;后者成为从节点。默认情况下每一台redis服务器…

matlab编程基础

1数据结构 1.1常量与变量 变量命名第一个字符必须为字母&#xff0c;变量可以由数字、字母、下划线组成&#xff0c;区分大小写。 常量&#xff1a; 1.2 数值型数据 双精度&#xff1a;64位 智能存储15位左右十进制数 double() 单精度 &#xff1a;32位&#xff0c;single…

C++ 类和对象下 [补充]

文章目录 友元内部类内部类是外部类的天生友元 匿名对象匿名对象的特性 友元 友元函数 重载operator<< 输出自定义类型 比如日期类的这个重载&#xff0c;就是解决类外访问私有变量的例子&#xff0c;设计成了友元函数。 说明&#xff1a; 友元函数可访问类的私有和保护成…

MS31703直流栅极驱动可pin对pin兼容DRV8703

DRV870x-Q1 器件是一款小型单通道 H 桥栅极驱动器&#xff0c;它使用四个外部 N 通道 MOSFET&#xff0c;旨在驱动一个双向刷式直流电机。 PH/EN、独立 H 桥或 PWM 接口允许轻松连接到控制器电路。内部传感放大器提供可调的电流控制。集成的电荷泵可提供 100% 占空比支持&#…

自动化测试 —— Airtest

最近在做APP自动化过程中&#xff0c;有调研Airtest框架的使用&#xff0c;便初步介绍一下Airtest框架的使用&#xff0c;有兴趣的小伙伴&#xff0c;可以一起探讨学习~ (一&#xff09;背景 Airtest是什么&#xff1f; Airtest是一款基于Python的、跨平台的UI自动化测试框架&…

数据结构与算法(三)

一、队列 队列&#xff08;queue&#xff09;:它是一种一种运算受限的线性表&#xff0c;FIFO&#xff08;先进先出&#xff09; 栈&#xff1a;后进先出 受限之处&#xff1a;它只允许表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;在表的后端(rear)进行插…