c语言指针刨析

139 阅读3分钟

指针

什么是指针?

  • 指针就是地址,那么地址的本质是什么?地址就是数据,那么数据可不可以保存在变量空间里呢?当然可以
  • 有没有指针变量的概念?
  • 保存指针数据的变量就叫做指针变量。指针是指向一块数据,指针变量是一个变量里面保存至指针地址。
  • 概念上叫指针,但是我们经常使用的是指针变量。
int main()
{
	int a = 10;
	int* p = &a;
	// 指针就是地址
	// 指针变量本质是变量,然后里面保存的地址(指针)值
	// 指针变量:空间(左值)+内容(右值:地址)
	p = (int*)0x1234;// p变量的空间:左值
	int* q = p;// p变量的内容:右值,就是刚刚的0x1234,指针 == 指针变量

	return 0;
}
  • 为什么要存在指针?
    • 为了cpu寻址的效率
  • 取地址时,取出来的永远是地址最小的那个
  • 一个整型,4个字节,那么应该有4个地址!那么&a取了哪一个地址?那么如何全部访问这4个字节。
    • 取出来的永远是地址最小的那个
    • 根据类型连续访问4个字节

指针的解引用

  • 在同类型情况下,对指针解引用代表指针所指向的目标。
  • 在我们 *p 时访问的是右值
  • 通过指针变量访问是一种间接访问.
double * p = NULL;// NULL -> 0
*p = 10.0;
// *p 拿到的是 NULL相当于 0
// 会出错,写入错误,访问的右值

数组

  • 数组是具有相同类型的集合。
  • 数组是线性连续且递增的!
  • 在开辟空间的角度,不应该把数组认为一个个独立的元素,应该整体开辟空间,整体释放。
  • 在main函数中定义,那么同样在栈上开辟空间。
  • 我们发现&arr[0] < &arr[1] < &arr[2] ... ... &arr[9].
  • 对指针 +1,其实是指向其类型的大小。
int main()
{
	char* c = NULL;
	int* i = NULL;
	short* s = NULL;
	double* d = NULL;

	printf("%d \n", c + 1);// 1
	printf("%d \n", i + 1);// 4 
	printf("%d \n", s + 1);// 2
	printf("%d \n", d + 1);// 8

	return 0;
}
  • 首元素的地址,数组地址为什么取出来的值一样?
    • 因为首元素的地址和数组的地址,在地址对应的字节是重叠的!所以,地址数据值相等。
    • 取出的地址是众多地址最低位。
int main()
{
	char arr[5] = { 0 };
	printf("%p \n", &arr[0]);// 数组首元素地址
	printf("%p \n", &arr[0] +1);// +1

	printf("%p \n", &arr);// 数组的地址
	printf("%p \n", &arr +1);// + 整个数组的大小
	return 0;
}

左右值

  • 数组名在做右值的时候,是首元素的地址。
  • 一个能够充当左值的,一定要有对应的空间。
  • 数组只能整体初始化,不能整体赋值,不能作为左值。
  • 数组能【】号索引的方式一个一个存放。
  • 总结:
    • 指针和数组没关系。
int main()
{
	char arr[5] = { 0 };

	char* p = arr;// 数组首元素地址

	return 0;
}
  • c语言没有字符串类型,有字符串。
int main()
{
	// 不可修改 操作系统保护,真正意义上不能被修改
	const char* str = "hello bit";

	// 在栈上开辟空间,可被访问修改
	char arr[] = "hello world";

	return 0;
}
  • 数组传参,发生降维,降维成指针
  • 为什么要将维?如果不降维就要发生数组拷贝,函数调用效率低,降维成指针
  • 所有数组,传参都会降维成指针,降维成指向其内部元素类型的指针
  • 在c中,任何函数调用,只要有形参实例化,必定形成临时拷贝!!!
void show(int* arrp, const int num)
{
	for (int i = 0; i < num; i++)
	{
		printf("%d \n", *arrp);
		arrp += 1;
	}
}

int main()
{
	int arr[] = { 1,2,3 };
	int num = sizeof(arr) / sizeof(arr[0]);

	show(arr, num);
	return 0;
}

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

int main()
{
	int a[5] = { 1,2,3,4,5 };
	int* ptr = (int*)(&a + 1);
	printf("%d %d \n", *(a + 1), *(ptr - 1));// 2 5
	// *(ptr - 1) 这里不会改变 *ptr ,这是表达式,不是这样的赋值 *ptr = *ptr -1 
	return 0;
}

指针数组,数组指针

  • [] > * 优先级 int p[10]; 指针数组,数组里面存储的是 int 类型的指针
  • int (*p)[10]; 整型数组指针,指针可以指向任何合法的类型变量。
  • 数组内部可以放置任何类型(内置,指针,结构体,联合体,数组)的内容!
  • 数组的降维,如果传递一个数组,或者多维数组,给一个函数,函数的形参会进行降维成指向成内部元素的指针,可以使用正常的数组访问到,不必使用指针方式。
// 这里的 arr[] 会降维 int*指针
void test(int arr[])
{
	for (int i = 0; i < 5; i++)
	{
		printf("%d \n", arr[i]);
	}
}

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	test(arr);
	// 两个地址相同
	return 0;
}

地址的强制转化

  • 强制类型转化,改变的是对特定内容的看待方式,在c中,就是只改变其类型。
  • 强制转化的价值。
    • 不让编译器报警
    • 给程序员看

二维数组

  • 所有维度的数组都是线性,连续,递增
  • 遍历数组
int main()
{
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
        // 两边的类型要相同
	int* p = (int *)arr;

	for (int i = 0; i < 3 * 4; i++)
	{
		printf("%d \n", (*p+i));
	}

	return 0;
}

  • 指针相减代表两个指针之间所经历的元素个数。

函数指针

  • 函数也是有地址的,为什么?。

    • 函数是代码的一部分,程序运行的时候,也要加载进内存,以供cpu后续寻址访问,函数也有地址
  • 函数只关心起始代码在哪里开始

  • 函数的地址是数据,就能通过变量去保存

  • void(*p)();

    • 首先确定是否是指针(*p)
    • 然后看返回类型void
    • 参数空()
void test()
{
	printf("hello");
}

int main()
{
	printf("%p \n", test);
	// 等价的 
	printf("%p \n", &test);
        
        // (*p)指针 ()参数
	void (*p)() = test;// 函数指针

	return 0;
}