c 语言指针学习

137 阅读6分钟

指针

在计算机科学中,指针( Pointer )是编程语言中的一个对象,利用地址,它的值直接指向( points to )存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为"“指针"。意思是通过它能找到以它为地址的内存单元。 指针就是变量,用来存放地址的变量(存放在指针中的值都被当成地址处理)。

  1. 一个小的内存单元到底有多大?(一个字节)
  2. 如何编址?
一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线那么假设每根地址线在寻址的产生的是一个电信号正点/负电(1或者0)
那么32根地址线产生的地址就会是:
00000000  00000000  00000000  00000000
... ... 
11111111 11111111 11111111 11111111

总结:

  1. 指针是用来存放地址的,地址是唯一标示一块地址空间的。
  2. 指针的大小在32位平台是4个字节,在64位平台是8个字节。

指针类型的意义

指针类型不能随便写是有意义的。

  1. 指针类型决定了:指针解引用的权限有多大
  2. 指针类型决定了指针,走一步能走多远(步长)
int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 10;
	/*
	*	第一种情况
		因为 解引用是 int 类型 内存中就是 00000000
	*/

	int b = 0x55667799;
	char* po = &b;
	*po = 5;
	/*
		第二种情况
		解引用是 char 类型 因为 char 是一个字节
		内存中为 05 77 66 55 值改变了一个字节
	*/

	return 0;
}

// -----------------------------------分割线-----------------------------------

int main()
{
	int arr[10] = { 0 };
	int* p = &arr;
	char* pc = &arr;
	printf("%p \n", p);// 000000BFCF75F998
	printf("%p \n", pc);// 000000BFCF75F998

	printf("%p \n", p + 1);// 00000016EC4FF57C
	printf("%p \n", pc + 1);// 00000016EC4FF579
	/*
		一开始存储的地址相同,但是进行计算因为 p 是 int 类型 加 4个字节
		pc 是 char 类型 加一个字节 跳过其他。
	*/

	return 0;
}

// -----------------------------------分割线-----------------------------------

int main()
{
	int arr[10] = { 0 };
	int* pa = &arr;
	for (int i = 0;10 > i;i++)
	{
		*(pa + i) = i+1;
	}
	// pa + i 其实是下标为 i 的地址
	// 1 2 3 4 5 6 7 8 9 10
	// 如果想当成一个整型来访问就使用int类型指针
	// 操作一个字节一个字节 的访问就使用 char类型
	// char 01 01 01 01 他会访问每一个字节并改变
	return 0;
}
  • int 指针 +1 跳过一个整型,char 指针 +1 跳过一个字符,数组指针 +1 跳过一个数组

野指针

  • 概念︰野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
int main()
{
	// 这里的 p 就是一个野指针
	int *p;// p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
	*p = 20;// 非法访问内存
	return 0;
}
// ---------------------------------------------------------------------------------
// 越界访问
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	// 0 - 10 包括 10 ,一共 11 个元素,数组本身10个元素
	for (int i = 0; 10 >= i; i++)
	{
		// 最后一个元素就会 越界访问
		*p = i;
		p++;
	}
	return 0;
}

// ------------------------------------------------------------------
// 指针访问的空间 被释放了

int* text()
{
	int a = 10;
	return &a;// 当这个函数执行完毕后 a变量 的生命周期就结束了
}			// 返回的只是一个地址

int main()
{
	int* p = text();// 解 出一个地址
	*p = 20;	// 这个时候这个地址里面已经没有 10了 是一个不存在的
	return 0;
}

如何避免野指针

  • 指针初始化
  • 当前不知道应该初始化什么地址的时候,直接初始化NULL
  • 明确知道初始化的值
  • 小心越界(c语言本身不会检查数据的越界行为的)
  • 指针指向空间释放及时置为NULL
  • 指针使用之前检查有效性(指针为NULL时是不能用的)

指针的运算

  • 指针 + - 整数
  • 指针 - 指针
  • 指针的关系运算

998.png

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

// -------------------------------------------------------------------------

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d", &arr[9] - &arr[0]);// 9
	// 指针减去指针 得到的两个指针之间元素的个数
	return 0;
}
  • 指针和指针相减的前提是 指针指向同一块空间
int my_strlen(char * str)
{
	char* stau = str;
	while (*str != '\0')
	{
		str++;
	}
	// 指针 减 指针
	return str - stau;
}

int main()
{
	char arr[] = "abc";
	// 求字符串长度
	int i = my_strlen(arr);
	printf("%d", i);
	return 0;
}

指针和数组

  • 数组名是数组首元素的地址
int main()
{
	int arr[10] = { 0 };
	// arr 数组名 是数组首元素的地址
	printf("%p", arr);// 0000003D9032FC98
	printf("%p", &arr[0]);// 0000003D9032FC98
	return 0;
}

// -----------------------------------------------------------------

int main()
{
	int arr[10] = { 0 };
	int* pa = arr;
	for (int i = 0; 10 > i; i++)
	{
		printf("%p <===> %p \n", &arr[i], pa + i);
		// &arr[i] == pa+i
		//*(pa + i) = 10;
	}
	return 0;
}

ddd.png

数组名

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	// [] 是一个操作符 2和arr是两个操作数
	// a+b == b+a
	printf("%d \n", arr[2]);// 3 arr[2] ==> *(arr+2)
	printf("%d \n", 2[arr]); // 3
	printf("%d \n", p[2]); // 3 p[2] ==> *(p+2)
	printf("%d \n", 2[p]); // 3
	/*
		arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) == 2[arr]
		2[arr] <==> *(2+arr)
	*/
	
	return 0;
}

二级指针

rr02.png

指针数组

指针数组是指针还是数组?

答案:是数组。是存放指针的数组

int main()
{
	int arr[10];// 整型数组 - 存放整型的数组
	char arr1[10];// 字符数组 - 存放字符的数组
	// 指针数组 - 存放指针的数组

	int* par[5];// 整型指针的数组

	return 0;
}

结构体初阶

结构是一些值的集合,但是值的类型可以不同。

结构的成员可以是 变量、数组、指针、甚至是其他结构体。

struct B
{
	char name[20];
	int id;
};

struct Stu
{
	// 结构体的成员是结构体
	struct B bt;
	// 成员变量
	char name[20];// 名字
	int age;
	char id[10];
}s1,s2;// 也是结构体变量
// s1,s2 全局变量
// 要是在 main函数中创建 就是局部变量

void print1(struct Stu t)
{
	// 这个 t 就是传递进来的参数
	printf("%d %s \n", t.age, t.name);// 16 张三
}

void print2(struct Stu * z)
{
	printf("%d %s", z->age, z->name);// 16 张三
}

int main()
{
	// 初始化 
	struct Stu b = { {"李四",1652},"张三",16,"0112"};

	// 访问 . ->
	printf("%d \n", b.age);
	printf("%s \n", b.id);
	printf("%s \n", b.bt.name);

	// 我们总会拿到 struct 中成员的地址
	struct Stu* ps = &b;
	printf("%d \n", (*ps).age);// 16
	// 但是我们还可以写成
	printf("%d \n", ps->age);// 16

	print1(b); // 传值调用

	print2(&b);// 传址调用 尽量选择这种 效率高 不会临时拷贝

	return 0;
}

结论:结构体传参的时候,要传结构体的地址