C语言——指针详解 上

C语言——指针详解 上

指针基础知识详解上

  1. 内存单元的编号==地址==指针
  2. 指针变量
int main()
{
int a = 10;
int* pa = &a;//取出a的地址并存储到指针变量pa中 
return 0;

  1. 指针类型
int a = 10;
int* pa = &a;//* 代表pa是一个指针变量,int代表该变量指向的类型是整形(int)类的对象

int*类型是存放地址的,int类型存放变量。

  1. 解引用操作符
int a = 10;
int* pa = &a;
*pa = 5;

其中pa为存放地址的指针变量,*pa的意思就是通过pa中存放的地址,找到指向的空间, 其实*pa就是变量a。

  1. 指针变量的大小
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit(即4个字节)--即sizeof(type*)= 4;
//64位平台下地址是64个bit(即8个字节)--即sizeof(type*)= 8;
  1. 指针的类型与解引用的关系

指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。

例如: char* 的指针就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节。

  1. 指针加减整数的运算
 #include <stdio.h>
 int main()
 {
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 printf("%p\n", &n);
 printf("%p\n", pc);//int*强转为char*
 printf("%p\n", pc+1);
 printf("%p\n", pi);//int*类型
 printf("%p\n", pi+1);
 return  0;
 }

由此可见,char* 类型的指针变量+1跳过1个字节,int* 类型的指针变量+1跳过了4个字节。

结论:指针的类型决定了指针向前或者向后走一步距离多大。

  1. const修饰指针
  • const如果放在 * 的左边,可以修改指针的指向的对象(即可以从&n改为&m),但是无法修改该指针指向变量的值(即不可以把n=5改为n=10)。

  • const如果放在 * 的右边,可以修改该指针指向变量的值(即可以把n=5改为n=10),但是无法修改指针指向的对象(即不可以从&n改为&m)。

结论:左不改数值,右不改地址。

  1. 指针的运算

指针的基本运算有三种,分别是:

  • 指针±整数
  • 指针-指针
  • 指针的关系运算
  1. 指针±整数
//1.指针+-整数
int main()
{
	int arr[] = { 2,4,6,8,10,12,14,16,18,20 };
	int* p = arr;//数组名为数组的第一个元素的地址
	for (int i = 0; i < 10; i++) {
		printf("%d ", *(p + i));//+i为第i个元素的地址,解引用可以获得arr[i]
	}
	return  0;
}

输出结果:

注意:数组的排列方式为由低地址到高地址方向排列,数组名为数组首元素的地址。

  1. 指针-指针
//2.指针-指针(strlen函数的实现)
int strlen(char* s)
{
	char* p = s;//字符串的结尾是’\0‘,刚开始p的地址为s,最后为\0的地址。
	while (*p != '\0') {
		p++;
	}
	return p - s;//两地址之间相邻元素的差值。
}
int main()
{
	char str[] = "abcdefg";
	char* s = str;//数组名为首元素的地址,s获得的是该字符串中‘a'的地址。
	int r = strlen(s);
	printf("%d\n", r);
	return  0;
}

注意:两个指针相减,为两个地址之间相邻元素的个数,只有在同一数组中才可以相减。

  1. 指针的关系运算
//指针的⼤⼩⽐较
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz) {	//指针+-整数
		printf("%d ", *p);
		p++;
	}
	return 0;
}
  1. 野指针

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

  • 常见的野指针:
  1. 未初始化:
int* m;//局部变量指针未初始化,默认为随机值
*m = 10;
  1. 越界访问:
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 11; i++){ //当指针指向的范围超出数组arr的范围时,p就是野指针
	*p = i;
	p++;
}
  1. 指针指向的空间释放
  • 如何避免野指针:
  1. 指针初始化

如果不知道指针应该指向哪,可以给指针赋值 NULL, NULL 是C语言中定义的⼀个标识符常量,值是0。0也是地址,这个地址是无法使用的,读写该地址就会报错。

int main()
{
int num = 10;
int*p1 = &num;
int*p2 = NULL;
return 0;
}
  1. 小心指针越界
  2. 指针变量不再使用时,及时置NULL,指针使用之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使用指针之前可以判断指针是否为NULL。

  1. assert断言

<assert.h> 头文件定义了宏 assert( ) ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为断言。

在assert()中,如果该表达式为真,没有任何作用,程序继续运行。如果该表达式为假,则程序就会报错。并且显示没有通过的表达式,以及包含这个表达式的文件名与行号。

如果已经确认程序没有问 题,不需要再做断言,就在 #include<assert.h> 语句的前面,定义⼀个宏NDEBUG。

#define NDEBUG
#include<assert.h>
  1. 指针的使用和传址调用
  • 模拟strlen函数的实现
//模拟strlen函数的实现
#include<stdio.h>
int myStrlen(char* p)
{
	int cnt = 0;
	while (*p != 0) {
		cnt++;
		p++;
	}
	return cnt;
}
int main()
{
	char str[] = "abcdefg";
	char* p = str;
	printf("%d ", myStrlen(p));
	return 0;
}
  • 用函数实现两个变量的交换
//用swap函数实现两个变量的交换
#include<stdio.h>
void swap(int* m, int* n)
{
	int tmp = *m;
	*m = *n;
	*n = tmp;
}
int main()
{
	int a = 10, b = 100;
	swap(&a, &b);
	printf("a=%d b=%d", a, b);
	return 0;
}

结论:实参传递给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参。
13. 对数组名的理解

  • 数组名就是数组首元素(第一个元素)的地址。
int* p=arr[0];
int* q=arr;
//指针p与指针q是完全相同的
  • &arr,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的),尽管他们在数值上大小相同。
int* p=arr;
int* q=&arr;
//p+1为跳过该数组的一个元素
//q+1为跳过整个数组

注意:sizeof为特殊情况,sizeof(arr) 为计算整个数组的大小,单位为字节(B)。

  1. 指针访问数组
#include<stdio.h>
int main()
{
	int arr[5] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
    //指针加减整数的应用 
	for (int i = 0; i < sz; i++) {
		scanf("%d", p + i);
	}
    //指针解引用的应用
	for (int j = 0; j < sz; j++) {
		printf("%d ", *(p + j));
	}
	return 0;
}

注意:在第13行中,将 *printf("%d ", (p + j)); 换成 printf("%d ", p[j]); 也可以正常打印。

同理 arr[i] 应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

  1. 一维数组传参的本质

在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址,所以在函数内部是没办法求的数组元素个数的。

#include<stdio.h>
void test1(int arr[])//写为数组形式
{
	int k = sizeof(arr) / sizeof(arr[0]);
	printf("%d", k);
}
void test2(int* arr)//写为指针形式
{
	int k = sizeof(arr) / sizeof(arr[0]);
	printf("%d", k);
}
//test1函数与test2函数等价
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	test1(arr);
    test2(arr);
	return 0;
}

输出结果:在x64环境下为2,在x86环境下为1.

总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

  1. 二级指针

指针变量也是变量,指针变量的地址存放于二级指针中。
17. 指针数组

我们可以类比于字符数组是用来存放字符的,那么指针数组就是用来存放指针变量的,指针数组的每个元素是地址,又可以指向一块区域。

  1. 用指针数组模拟二维数组
#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*,可以存放与parr数组中
	int* parr[] = { arr1,arr2,arr3 };
	//第一层循环为选择parr中的元素(arr1、arr2或arr3)
	for (int i = 0; i < 3; i++) {
		//第二层循环为选择arri中的元素(1、2、3......)
		for (int j = 0; j < 5; j++) {
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}
  • 注意:parr[ i ]是访问parr数组的元素,parr[ i ]找到的数组元素指向了整型一维数组(arri),parr[ i ][ j ]就是整型⼀维数组中的元素。
    但是上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行在内存中,不是连续储存的。
  1. 字符指针变量
  • 一般的字符指针变量
int main()
{
	char ch = 'w';
	char* pch = &ch;
	*pch = 'x';
	printf("%c", ch);
	return 0;
}
  • 还有一种使用方法
int main()
{
	const char* pstr = "hello";
	printf("%s", pstr);
	return 0;
}

注意:const在 * 的左面为不能修改该指针指向变量的值,const在 * 的右面为不能修改该指针指向的变量。

代码 const char pstr = “hello”; 的本质为把该字符串首字符(h)的地址放到了该指针变量中。

  • 使用示例:
int main()
{
	char str1[] = "hello";
	char str2[] = "hello";
	const char* str3 = "hello";
	const char* str4 = "hello";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}

输出结果:
str1 and str2 are not same
str3 and str4 are same

str3和str4指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域, 当几个指针指向同⼀个字符串的时候,他们会指向同一块内存。

​ 但是用相同的常量字符串去初始化不同的数组时就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

  1. 数组指针变量

指针数组 int parr[ ] = { arr1,arr2,arr3 };* 为存放指针变量的数组,它的本质是一个数组

而数组指针,存放的是数组的地址,能够指向数组的指针变量,它的本质为指针

整型指针: int* pa=&a;

数组指针:int (*p)[ 10 ];

注意:p先和*结合,说明p是⼀个指针变量,然后指向的是⼀个大小为10个整型元素的数组。所以 p是⼀个指针,指向⼀个数组,叫数组指针。 [ ]的优先级要高于*,所以必须加上()来保证p先和*结合。

  • 数组指针变量的初始化

如果要存放个数组的地址,就需要存放在数组指针变量中。

int arr[10] = { 0 };
int(*p)[10] = &arr;
//其中的变量p就存放着该数组的地址。

注意 :类型要完全匹配,两个数组中元素的个数要保持完全一致。

  1. 二维数组传参的本质

在二维数组中,可以将每一个一维数组看作二维数组的一个元素,也就是说,二维数组的每个元素是⼀个⼀维数组。那么二维数组的首元素就是第⼀行,是一个一维数组。

例如:

int array[3][5] = { 0 };
//其中的[3]可以理解为这个二维数组有三个元素(即三个一维数组)
//后面的[5]可以理解为二维数组中的每一个元素(一维数组)中有五个元素

所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的一维数组的地址。

  • 二维数组传参本质上也是传递了地址,传递的是第一行的整个一维数组的地址。(注意不是一维数组首元素的地址)

所以二维数组要用数组指针变量。

二维数组传参示例:

#include<stdio.h>
//(*p)表示该二维数组首元素(即第一行的一维数组)的地址
void arrPrint(int(*p)[5], int h, int l)
{
	for (int i = 0; i < h; i++) {
		for (int j = 0; j < l; j++) {
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	arrPrint(arr, 3, 5);
	return 0;
}

理解方法:

printf("%d ", *(*(p + i) + j));
//p就是第一行一维数组的地址,+i表示往下一个一维数组(即二维数组的元素)跳转。
//+j表示在一个一维数组中,往后跳一个元素。
  • 31
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值