梦开始的地方——c语言指针入门(代码片段)

爱敲代码的三毛 爱敲代码的三毛     2022-12-05     798

关键词:

文章目录


指针入门

1.指针概念

指针(Pointer) 是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元 。

指针是个变量,存放内存单元的地址(编号) ,一个内存单元的大小是一字节,也就是8比特,是一串8比特的二进制数。

#include <stdio.h>
int main()

	int num = 10;
	int* p = &num; //取出num的地址,p里面存放的就是num变量的首地址


	return 0;

在32位的机器上存在着32根地址线,64位机器上存在着64根地址线。每根地址线对应1个比特位,通电后产生了0和1的正电和负电,对应着二进制位。也就是说32位机器上一个地址是32个比特位,也就是4个字节。而64位机器上地址是64个比特位,也就是8个字节

我们知道指针存放的是地址,那么这样说的话32位机器和64位机器上的指针大小是不一样的。

  • 在32位机器上,有32根地址线,所以指针大小要4个字节才能存放地址
  • 在64位机器上,如果有64根地址线,那么指针要是8个字节才能把地址存放起来

2. 指针和指针类型

我们都知道变量有不同的类型,那么指针也是有不同的类型的。

	int* p1 = NULL;
	short* p2 = NULL;
	float* p3 = NULL;
	double* p3 = NULL;
	char* p4 = NULL;
	long* p5 = NULL;

以上可以看到,指针变量的定义是 type+*,分别对应着各种变量的类型。

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

来看代码

#include <stdio.h>
int main()

	int* a = 10;
	int* p1 = &a;
	char c = '1';
	char* p2 = &c;

	printf("%p\\n",p1);
	printf("%p\\n",p1+1);
	printf("====================\\n");
	printf("%p\\n",p2);
	printf("%p\\n",p2+1);

	return 0;

运行结果,从下面的运行结果我们发现int型的指针+1跳过了4个字节,而char类型的指针只跳过了1个字节。

00B5FE30
00B5FE34
====================
00B5FE1B
00B5FE1C

也就是说指针的类型决定了指针+1/-1能走多大距离。

再来看一段代码,我们通过解引用来修改变量的值

#include <stdio.h>
int main()

	int a = 0x11223344;
	int* p1 = &a;
	*p1 = 0;


	return 0;

这是修改前a在内存中的值

这是修改后内存中的值,我们发现4个字节的值全被改为0了

接着来看这么一段代码,,同样是&a,不过这里是哪char类型的指针来接受int类型的地址,因为指针大小是相同的所以是可以放得下的

#include <stdio.h>
int main()

	int a = 0x11223344;
	char* p1 = &a;
	*p1 = 0;


	return 0;

同样是修改值,我们发现在内存中只有一个字节的值被修改了。

所以指针的类型的又一个作用就是:决定了指针的访问权限,也就是能访问几个字节。

#include <stdio.h>
int main()

	int arr[10] =  0 ;
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i ++)
	
		*(p + i) = i;
	
	i = 0;
	for (i = 0; i < 10; i++)
	
		printf("%d ", arr[i]);
	

	return 0;

这段代码输出

0 1 2 3 4 5 6 7 8 9

小结

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节) 。
  • 指针的类型决定了指针+1/-1能走多大距离

3. 野指针

什么是野指针?

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

造成野指针的原因

  1. 指针指向的空间已经释放

    下面这段代码正确输出10,貌似没什么问题

    #include <stdio.h>
    int* test()
    
    	int a = 10;
    
    
    	return &a;
    
    int main()
    
    	int* p = test();
    	printf("%d", *p);
    
    
    	return 0;
    
    

    再来看看,我们加上语句打印的代码

    #include <stdio.h>
    int* test()
    
    	int a = 10;
    
    
    	return &a;
    
    int main()
    
    	int* p = test();
    	printf("hello world!\\n");
    	printf("%d", *p);
    
    
    	return 0;
    
    
    //输出???
    

    这段代码的打印结果就不能确定了,打印的就是随机值。这就是个典型的野指针

    p得到的地址之后,地址指向的空间已经释放了,所以这个时候的p就是野指针

  2. 指针未初始化

    下面这段代码也是野指针,指针变量未初始化,里面放的就是随机值,也就是编译器给我们分配的地址。这段代码运行是直击报错的

    #include <stdio.h>
    
    int main()
    
    	int* p;
    	*p = 10;
    	printf("%d\\n", *p);
    
    	return 0;
    
    
  3. 指针越界访问

    假设我们对一个指针越界访问,也就是访问程序外的内存地址。也是会造成野指针的。

    下面数组最后一个元素是arr[4],尝试使用指针越界访问,打印的就是随机值。

    当指针指向的范围超出数组的范围的时候,此时的p就是野指针

    #include <stdio.h>
    
    int main()
    
    	int arr[5] =  0 ;
    	int* p = &arr;
    	int i = 0;
    	for (i = 0; i < 6; i++)
    	
    		printf("%d ", *(p + i));
    	
    
    	return 0;
    
    

如何避免野指针

  1. 定义指针变量前记得初始化或者让其指向NULL
  2. 注意不要让指针越界访问
  3. 指针指向的空间释放时,及时让该指针指向NULL

来看一段代码

#include <stdio.h>

int main()

	int arr[5] =  0 ;
	int* p = &arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	
		printf("%d ", *(p + i));
	
	//假设指针已经不使用
	int num = 20;
	p = NULL;
	if (p != NULL)
	
		*p = 10;
	
	else
	
		p = &num;
	
	printf("%d\\n", *p);

	return 0;

注意,NULL本身是等于0的,在源代码中定义了,只是将其强转成了 viod*

#define NULL ((void *)0)

NULL指向的空间是不能访问的

其实指针变量p本身存的就是一个16进制的地址,语法上是支持直接赋值地址给指针变量的,虽然语法上是支持,但这属于非法访问。

#include <stdio.h>

int main()

	int* p = 0x00112233F5;

	printf("%p\\n", p);
	return 0;

4. 指针的运算

指针加减整数

前面已经提到过对指针进行加减操作,指针能走多远取决于指针的类型。比如int类型的指针+1就是跳过4个字节,char类型的指针+1就是跳过1个字节,同理-1就是往回退几个字节

#include <stdio.h>

int main()

	int arr[5] =  0 ;
	int* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	
		*(p + i) = i;
	
	for (i = 0; i < 5; i++)
	
		printf("%d ", *(p + i));
	

	return 0;

指针的运算关系

来看一段代码

#include <stdio.h>

int main()

	int arr[10] =  0 ;
	char ch[5] =  0 ;

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

	return 0;

运行结果

9
-9
4
-4

&arr[index]是一个地址,本质上就是一个指针,两个指针相减得到了数组元素的个数。

所以

  • 指针-指针 绝对值的是指针和指针之间的元素个数
  • 注意:指针-指针 计算的前提条件是:两个指针指向的是同一块连续的空间的

通过指针-指针模拟实现strlen函数

int my_strlen(char* str)

	char* index = str;
	while (*index != '\\0')
	
		index++;
	

	return index - str;

指针的关系运算

我们知道指针可以加减操作,来看这么一段代码,这个代码的指针p指向数组的最后一个元素,然后不断减减,判断当前地址是否要大于等于数组第一元素的地址,完成了数组的赋值。

#include <stdio.h>

int main()

	int arr[10] =  0 ;
	int* p = arr[10];

	for (p = &arr[9]; p >= &arr[0]; p--)
	
		*p = 10;
	
	int i = 0;
	for (i = 0; i < 10; i++)
	
		printf("%d ", arr[i]);
	
	return 0;


这个代码输出10个10,表面上好像没有问题,大部分编译器都是这个结果。

但是需要注意的是C语言标准规定

**允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许
与指向第一个元素之前的那个内存位置的指针进行比较 **

因为这里for循环最后一次判断比较的是数组首元素前面的那块地址,但是C语言标准不建议这么做。这么做可能会出现问题

我们可以把代码改进一下,这样就是拿数组最后一个元素的地址和它后面的地址比较了

#include <stdio.h>

int main()

	int arr[10] =  0 ;
	int* p = arr[10];

	for (p = &arr[0]; p < &arr[10]; p++)
	
		*p = 10;
	
	int i = 0;
	for (i = 0; i < 10; i++)
	
		printf("%d ", arr[i]);
	
	return 0;

5.指针和数组

指针不是数组,数组也不是指针。但是数组可以通过指针来访问

来看一段代码

#include <stdio.h>

int main()

	int arr[5] =  0 ;
	printf("arr == %p\\n", arr);
	printf("&arr[0] == %p\\n", &arr[0]);
	
	return 0;

输出

arr == 0000005DD98FF578
&arr[0] == 0000005DD98FF578

可以说名数组名就是数组的首元素地址.

既然数组名可以当做一个内存地址放到指针中,那么就可以通过指针来访问。

#include <stdio.h>

int main()

	int arr[5] =  0 ;
	int* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	
		printf("&arr[%d] = %p <===> p+%d = %p\\n",i ,&arr[i],i,p+i);
	
	return 0;

输出

&arr[0] = 000000B9FD36FC58 <===> p+0 = 000000B9FD36FC58
&arr[1] = 000000B9FD36FC5C <===> p+1 = 000000B9FD36FC5C
&arr[2] = 000000B9FD36FC60 <===> p+2 = 000000B9FD36FC60
&arr[3] = 000000B9FD36FC64 <===> p+3 = 000000B9FD36FC64
&arr[4] = 000000B9FD36FC68 <===> p+4 = 000000B9FD36FC68

二维数组名的地址也是首元素地址,二维数组的首元素是一位数组,所以二维数组名是二维数组第一行的地址。

6. 二级指针

指针是一个变量,我们知道是个变量在内存中就会有一个地址。那么指针变量的地址怎么存放?那么就可以使用二级指针来存放。

#include <stdio.h>

int main()

	int a = 100;
	int* p = &a;
	int** pp = &p;
	printf("%p\\n", p);//存的是a的地址
	printf("%p\\n", pp);//存的是p的地址
	printf("%p\\n", &pp);//取出二级指针pp的地址
	
	return 0;

运行结果

00000094626FFA24
00000094626FFA48
00000094626FFA68

pp是一个二级指针变量里面存的是一级指针p的地址,通过对pp进行解引用操作就能拿到p的地址,而p里面存的是变量a的地址,再次对p进行解引用操作就能得到变量a的值

#include <stdio.h>

int main()

	int a = 100;
	int* p = &a;
	int** pp = &p;
	printf("%d\\n", **pp);
	
	return 0;

多级指针

#include <stdio.h>

int main()

	int a = 100;
	int* p = &a;
	int** pp = &p;
	int*** ppp = &pp;
	int**** pppp = &ppp;

	printf("%d\\n", ****pppp);
	
	return 0;



//输出100

7. 指针数组

指针数组是指针还是数组?指针数组是存放指针的数组。

我们整形数组和字符型数组,里面存的是整形变量和字符变量。

int arr[5] =  0 ;
char str[] = "hello";

那么指针数组又是什么样子的呢?

下面这段代码我们知道对变量取地址得到的是一个地址也就是指针,把这个指针放到一个数组里,这个数组就是指针数组,[]表示是数组,int*是一个整形指针,所以这是一个整形的指针数组。

#include <stdio.h>

int main()

	int a = 10;
	int b = 20;
	int c = 30;

	int* arr[] =  &a,&b,&c ;
	
	return 0;


字符指针数组

我们知道字符数组可以写成指针的形式,存的就是字符串的首地址。那么我们把几个字符串放到一个数组中,就可以让一个字符指针数组来接收了。

字符指针数组里面存的都是字符串的首元素地址,可以直击通过%s来打印

#include <stdio.h>

int main()

	char* str = "hhh";//存放的是字符串的首地址

	char* arr[] =  "hello","world","linux" ;
	int i = 0;
	for (i = 0; i < 3; i查看详情  

梦开始的地方——c语言:函数指针+函数指针数组+指向函数指针数组的指针(代码片段)

文章目录一、函数指针1.函数指针定义2.函数指针的使用3.解读函数指针代码二、函数指针数组三、指向函数指针数组的指针四、回调函数一、函数指针1.函数指针定义整形指针是指向整形的指针:存放整形的地址数组指针是... 查看详情

梦开始的地方——c语言柔性数组(代码片段)

文章目录柔性数组什么是柔性数组?柔性数组的使用柔性数组的优点柔性数组什么是柔性数组?在C99中,结构体最后一个元素它允许是一个未知大小的数组,这就叫做柔性数组成员。这个概念听起来可能有点不可... 查看详情

梦开始的地方——c语言常用字符函数汇总(代码片段)

文章目录字符库函数1.strlen(求字符串长度)2.strcpy(字符串拷贝)3.strcat(字符串追加函数)4.strcmp(字符串比较)5.strncpy(字符串拷贝)6.strncat(字符串追加)7.strncmp(字符串比较)8.strstr(字符串查找)9.strtok(字符串分割)10.strerror(错误信息)字符库... 查看详情

梦开始的地方——c语言预处理+编译过程(代码片段)

文章目录C语言程序的编译(预处理)1.编译和链接1)编译的几个阶段预编译阶段编译阶段汇编阶段2)链接2.预处理1)预定义符号2)#define3)#和##4)带副作用的宏参数5)宏和函数对比3.常见预处理命令1)#undef2)命令行定义3)条件编译4)文件包含5... 查看详情

梦开始的地方——c语言中那些细节(代码片段)

文章目录static关键字1.static修饰局部变量2.static修饰全局变量3.static修饰函数函数的默认返回值隐式类型转换1.整形提升2.整型提升的意义3.算数转换static关键字1.static修饰局部变量生命周期延长:该变量不随函数结束而结束ÿ... 查看详情

梦开始的地方——c语言动态内存管理(malloc+calloc+realloc+free)(代码片段)

文章目录动态内存管理1.为什么需要动态内存分配?2.动态内存函数malloc&freecallocrealloc3.常见的动态内存错误对NULL解引用对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同... 查看详情

梦开始的地方——c语言数据在内存中的存储(整形+浮点型)(代码片段)

文章目录整形在内存中的存储1.数值类型的基本分类2.整形在内存中的存储1.原码、反码、补码2.内存中为什么要存放补码?3.大小端存储4.无符号有符号数练习5.有符号数无符号数小结浮点型在内存中的存储IEEE754整形在内存中... 查看详情

01梦开始的地方,阿仁的c++学习笔记(p2~)(代码片段)

...业需要的考虑和受大佬室友的影响,实习之余,本菜鸡将开始学习c++。用的是bilibil播放量最多的那个c++入门课程,环境是vscode,开此贴为笔记之用。1、第一个helloworld程序1#include<iostream>2usingnamespacestd;34intmain()56cout<<"hello... 查看详情

c语言指针的入门详细介绍(代码片段)

文章目录前言(一)指针初阶:一:什么是指针二:什么是野指针什么情况下会造成野指针呢?如何避免野指针三:指针的运算1.指针的加减整数运算2.指针-(减)指针3.使用指针减指针模仿写一个strlen四:指针和数... 查看详情

萌新的福利-c语言指针入门(代码片段)

先来看一段百度百科的对指针的介绍指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单... 查看详情

梦开始的地方,从最小二乘法开始学机器学习(代码片段)

梦开始的地方,从最小二乘法开始学机器学习从这篇博客开始,我们将逐步实现不同的机器学习代码,以此来深入学习了解不同的机器学习背后的原理~文章目录梦开始的地方,从最小二乘法开始学机器学习00.参考... 查看详情

梦开始的地方,从最小二乘法开始学机器学习(代码片段)

梦开始的地方,从最小二乘法开始学机器学习从这篇博客开始,我们将逐步实现不同的机器学习代码,以此来深入学习了解不同的机器学习背后的原理~文章目录梦开始的地方,从最小二乘法开始学机器学习00.参考... 查看详情

梦开始的地方——c语言(枚举+位段+联合体)(代码片段)

位段1.什么是位段?要想了解位段就得先学会使用结构体。文章链接——>详解结构体位段的声明和结构体是十分类似的,它们有两个不同之处位段的成员必须是int、unsignedint、signedint位段的成员名后面有一个冒号和数字... 查看详情

go语言入门四(复合类型数组切片指针)(代码片段)

...类型复合类型:把基础类型封装起来了。指针指针可是c语言中的重头戏,指针数组数组指针函数指针指针函数回调函数回调函数的数组一级指针二级指针三四五。。。。c语言指针有可以进行判断进行+-等操作go语言是不行的go语... 查看详情

c语言攻略-从零开始的c语言生活----初阶篇(代码片段)

各位大佬大家好啊!从今天开始正式的学习C语言,就废话不多说我所使用编译器:【VisualStudio2019】目录了解什么是C语言    C语言的发展史第一个C程序——梦开始的地方数据类型数据类型所占内存大小(sizeof关... 查看详情

c++入门基础教程:c语言的指针与结构体到底怎么用?(代码片段)

目录一、前言二、指针2.1指针与地址2.2指针与函数参数2.3指针与数组2.3.1指针与一维数组2.3.2 指针与二维数组2.4 字符指针与函数2.5 指针数组以及指向指针的指针2.5.1指针数组2.5.2指向指针的指针2.6 指向函数的指针三、结构体3.... 查看详情

入门c语言——初识c语言(代码片段)

一、引言本系列文章主要内容主要是我对C语言的初步认识以及知识小结。二、GET到的知识1、常见关键字(1)typedef(2)register(3)static(4)define定义的常量和宏2、指针(1)内存的使用(2)指针变量3、结构体1、常见关键字关键字功能auto创建自... 查看详情