C 语言指针进阶

1.0 指针的定义

指针是内存中一个最小单元的编号(内存单元的编号称之为地址【地址就是指针指针就是地址】)指针通常是用来存放内存地址的一个变量。本质上指针就是地址:口语上说的指针起始是指针变量,指针变量就是一个变量,是一个用于存放地址的变量,指针指向的就是地址,通过这个地址可以找到对应的内存单元。

指针变量创建:

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	// a 是一个整型变量,占用4个字节内存空间
	int a = 10;
	// pa 是一个指针变量,指针变量是用来存放地址的
	int* pa = &a;
	return 0;
}

总的来说,指针变量是用于存放地址的变量,(存放在指针中的值被当做是地址处理),在32位的平台下一个指针变量的大小是4个字节,在64位地址的机器上指针变量的大小是8个字节。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	char   *pc = NULL;
	short  *ps = NULL;
	int    *pi = NULL;
	double *pd = NULL;

	printf("%zu\n", sizeof(pc));
	printf("%zu\n", sizeof(ps));
	printf("%zu\n", sizeof(pi));
	printf("%zu\n", sizeof(pd));
	return 0;
}

2.0 指针与指针类型

指针类型的含义

  1. 指针的类型决定的指针被解引用的时候被访问几个字节【所谓的解引用是官方的叫法,实际上就去获取地址当中的值,或者说是将地址当中的值取出来】,例如:int * 的指针,解引用访问4个字节,char * 的指针解引用是访问一个字节,推广到其他的参数时也是一样的。
cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;

	char* pc = (char*)&a;
	*pc = 0;

	return 0;
}

**2. 指针的步长:**理解指针步长的意义

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	char* pc = (char *) & a;

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

	printf("pa = %p\n", pc);
	printf("pa = %p\n", pc + 1);
	return 0;
}

指针类型决定了访问内存一次可以访问几个字节,如果是char * 的指针类型,那么访问内存只能一个字节一个字节的进行访问,如果是int * 类型的指针类型,那么访问内存就是以一次4个字节进行访问, int * pc如果是pc + 1 的话那么就是跳过前面的4个字节。


注:不同类型指针占用类型大小相同的指针是不能混用的,因为两者存储在内存中的空间是不同的,int 类型存储的是int类型,float类型存储的是float类型。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int a = 0;
	int* pi = &a;  // pi 解引用访问4个字节,pi + 1 也是跳过4个字节
	float* pf =(float *)& a;// pf 解引用访问4个字节,pf + 1 也是跳过4个字节

	return 0;
}

3.0 野指针,指针,与指针运算

野指针的概念:指的是指针指向的位置是不可知的

cpp 复制代码
int main
{
	// p没有初始化,就意味着没有明确的指向
	// 一个局部变量不初始化,放的是随机值
   int *p;
   // 这样做的方式就是非法的访问内存,这个时候p就是野指针
   *p = 10;
}
cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int arr[10] = { 0 };
	// 数组名表示数组首元素地址arr[0]
	int* p = arr;
	// 指针的越界访问也会造成野指针
	for (int i = 0; i <= 10; i++) 
	{
		*p = i;
		p++;
	}
	return 0;
}

野指针:涉及到局部变量和全局变量知识

函数执行进入主函数:main

这个时候test() 函数被调用

通过return 返回a的地址地址返回后int a 由于数局部变量出函数之后被销毁

这个时候 int *p 通过地址可以找到a这个空间

但是这个时候a空间已经被销毁,p这个时候无法访问和使用这个空间,这个时候这个p属于是野指针。

cpp 复制代码
int * test()
{
   int a = 10;
   return &a;
}

int main()
{
   int *p = test();
   return 0;
}

4.0 如何避免野指针

NULL 的值相当于是0 ,0 空间是没有办法被指针访问的,避免野指针的方式可以在初始化不知道赋值为什么的时候赋值为一个NULL也就是空指针,同时在使用之前先判断指针是否为空,如果指针为空就不使用,如果指针不为空的时候就对指针变量进行相应的操作,具体的示例如下所示。

避免野指针:

1: 指针初始化,

2:小心数组下标越界,

3:指针指向空间释放及时设置为NULL,

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

5:使用指针之前先检查指针的有效性。

cpp 复制代码
int * p = NULL;
int mian(void)
{
	if(p != NULL)
	{
		*p3 = 100;
	}
	return 0;

}

指针加减整数运算

cpp 复制代码
#define N_VALUE 5
float values[N_VALUES];
float *vp

for(vp = &values[0]; vp < &values[N_VALUES];)
{
	*vp++ = 0;
}
cpp 复制代码
int main(void)
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr)/sizeof(arr[0]);
	

	for(i = 0; i < sz; i++)
	{
		// 把数组里面的值全部赋值为 1
		arr[i] = 1;
	}
	// int 类型的指针变量p,数组名等于数组首元素的地址
	int *p = arr;
	for(i = 0; i < sz; i++)
	{
		// *P 解引用:也就是取值的意思
		*p = 1;
		p++;
	
	}
	// 使用指针步长的方式访问数组,进行数组操作
	for(i = 0; i < sz; i++)
	{
		*(p + 1) = 1;
	}
	return 0;

}

5.0 指针加减指针

cpp 复制代码
int main(void)
{
    // 指针减去指针得到的绝对值得到的是指针之间元素的个数
	// 不是所有的指针都能相减,指向同一块空间的指针才能相减
	int arr[10] = { 0 };
	printf("%d\n",&arr[9] - &arr[0]); // 9
}

字符串长度:使用指针减去指针的方式

cpp 复制代码
int my_strlen(char *str)
{*

   // str 刚开始起始地址是a
	int count = 0;
	while(*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
// 指针 - 指针
int my_strlen(char *str)
{
	char *start = str;
	while(*str != '\0')
	{
		str++;
	}
	return (str - start);

}

int main(void)
{
	int len = my_strlen("abcdef");
	printf("%d\n",len);

}

指针的关系运算

cpp 复制代码
#define N_VALUES 5
float values[N_VALUES]
float *vp;

for(vp = &values[N_VALUES]; vp > &values[0];)
{
	*--p = 0;
}

C 语言标准规定指向指针的元素与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。


指针遍历访问数组

数组的定义:数组是一组相同元素的集合,指针变量是一个变量,存放的是地址,数组的数组名是首元素的地址 。

cpp 复制代码
int main(void)
{
	int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int *p = arr;
	int len = sizeof(arr)/sizeof(arr[0]);
	int i = 0
	for(i = 0; i < len; i++)
	{
		printf("%d ",*(p + i));
	}
    for(i = 0; i < len; i++)
    {
        // 打印输出的地址结果是相同的
        printf("%p-----------------%p\n",&arr[i],p + i);
    }
	return 0;
}

6.0 二级指针

引入一级指针的概念:

int a = 10 ; a 是一个int 类型的变量初始值为10

int * pa = &a; pa是一个指针,pa是一个指向int 类型的指针变量,

&a 把 变量a在内存中的地址赋值给指针变量pa ,pa可以通过这个地址找到a并获取a在变量当中的值并修改同时打印输出。

cpp 复制代码
int main()
{
	int a = 10;
	// pa 是一个一级指针变量
	int* pa = &a;
	*pa = 20;
	printf("%d ", a);

	return 0;
}

二级指针变量:程序输出的值是 30 注意理解:int **ppa 中

cpp 复制代码
int main()
{
	int a = 10;
	// pa 是一个一级指针变量
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 30;
	printf("%d ", a);
	return 0;
}

二级指针存放的是一级指针变量的地址

cpp 复制代码
int main()
{
	int a = 10;
	// pa 是一个一级指针变量
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 30;
	printf("%d ", a);
	return 0;
}

7.0 指针数组

【指针数组:存放指针的数组就是指针数组】

cpp 复制代码
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int arr[10];

	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	// parr 是存放指针的数组,称之为指针数组
	int* parr[10] = { &a, &b, &c };

	int i = 0;
	int len = sizeof(parr) / sizeof(parr[0]);
	for (i = 0; i < len; i++) 
	{
		printf("%d ", *(parr[i]));
	}
	return 0;
}

【补充:二维数组创建与遍历】

cpp 复制代码
/*
****************************
*  DEF    :  二级指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/15
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int arr[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++) 
	{
		for (j = 0; j < 4; j++) 
		{
			printf("%d  ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

指针模拟二维数组

cpp 复制代码
int main()
{
	int arrOne  [4] = { 1, 2, 3, 4 };
	int arrTwo  [4] = { 2, 3, 4, 5 };
	int arrThree[4] = { 6, 7, 8, 9 };

	int* parr[3] = { arrOne,  arrTwo, arrThree };

	int i = 0;
	for (i = 0; i < 3; i++) 
	{
		int j = 0;
		for (j = 0; j < 4; j++) 
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

字符指针

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

int main()
{
	char ch = 'c';
	char* p = &ch;
	*p = 'b';
	printf("%c\n", ch);

	const char* pc = "abcdef";
	printf("%s\n", pc);
	return 0;
}
cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	if (p1 == p2) 
	{
		printf("p1 == p2\n");
	}
	else 
	{
		printf("p1 != p2\n");
	}
	if (arr1 == arr2) 
	{
		printf("arr1 == arr2\n");
	}
	else 
	{
		printf("arr1 != arr2\n");
	}
	return 0;
}

8.0 指针数组


int arr[10]; // 表示整形数组

char ch[5]; // 表示字符数组

int *arr[6]; // 存放整形指针的数组

char * arr[5] // 存放字符类型的数组


int* parr[MAX_ARR_NUM] = { arr1, arr2, arr3 };

以上这段代码的含义是 创建指针数组 parr

注: parr 是一个指针变量 ,指向的是一个数组,数组中存放了三个变量,也就是数组元素的首地址,每个变量的类型是int * 【也就是创建了一个int* 类型,变量名字叫做parr的数组指针,执行每个数组元素的首地址,每个数组元素的类型为 int 】

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

#define MAX_ARR_NUM 3

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,5 };
	int arr3[] = { 1,2,3,4,5 };
	// 数组名表示数组首元素地址,数组指针存放的是三个数组的地址
	int* parr[MAX_ARR_NUM] = { arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < MAX_ARR_NUM; i++) 
	{
		int j = 0;
		for (j = 0; j < 5; j++) 
		{
             // *(parr + i ) == p [i];
             printf("%d ", parr[i][j]);
			printf("%d ", *(parr[i] + j));
		}
		printf("\n");
	}

	return 0;
}

注:*(parr + i) 等价于 parr[i] ,所以上面的代码有两种不同的遍历办法


以下是一些数组地址的补充阐释,为了更好的理解下面的知识

[数组指针 --- 指针-------指向数组的指针]

整型指针-------指向整型的指针

字符指针------指向字符的指针

int* p[10] p 是指针数组

int (*p)[10] p 是一个数组指针,p可以指向一个数组,该数组有10个元素,每个元素是int 类型。


数组名的理解

数组名通常表示数组首元素的地址,但是有两个例外,

1.sizeof(数组名),这里的数组表示的是整个数组,计算的是整个数组的大小,

2.&数组名,这里的数组名表示的依然是整个数组。所以取出的是整个数组的地址

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

#define MAX_ARR_NUM 3

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);

	int sz = sizeof(arr);
	printf("%d\n", sz);
	return 0;
}

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

#define MAX_ARR_NUM 3

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);

	// 这是一个数组的地址,&arr取出的是整个数组
	printf("%p\n", &arr);
	// &数组名 + 1 表示的是跳过整个数组
	printf("%p\n", &arr + 1);

	int sz = sizeof(arr);
	printf("%d\n", sz);
	return 0;
}

数组指针是用于存放数组的地址

cpp 复制代码
int main()
{
	// 数组:名字是arr
	int arr[10] = { 0 };
	// 指针: 数组名arr表示首元素的地址,将首元素的地址放到一个int类型的arr指针当中
	int* p = arr;
	// 数组指针:存放数组的地址,表示的是一个int类型的数组指针
	int(*p)[10] = &arr;
	return 0;
}

注意:上面的代码中 int * p = arr; p 是一个指针,指针的类型是 int * . int (*p)[10] = &arr ; 这是一一个数组指针,存储的是整个数组的,指针的类型是 int *[] 。

......

相关推荐
XiaoLeisj28 分钟前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师1 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉2 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer2 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq2 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
wheeldown2 小时前
【数据结构】选择排序
数据结构·算法·排序算法
记录成长java3 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山3 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
hikktn4 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
青花瓷4 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode