C深度解析——指针(上)

1、指针与指针变量

启动一个程序,系统在内存上给该程序分配一块内存空间。如果是32位系统,内存大小为4G。
内存空间是由一个个字节构成,每个字节系统会分配一个编号,称为地址

回顾C语言基础:
整型变量:int a = 10;,其中,a为整型变量,存储整数。
指针变量:int *p ,其中,p为指针类型。指针变量可理解为存指针(地址)的变量在32位系统中,因为地址编号格式为:0x 0000 0000,所以指针变量占4个字节。指针的实质就是地址

  1. 指针变量的定义和初始化
int main()
{
	int a = 10;
	//定义指针三步骤:1、* 与符号结合代表是一个变量; 2、要保存谁的地址,就将它的定义形式放在此处;3、用 *p 替换定义的变量
	int *p;
	//指针分析三步骤:
	//1、与 * 结合代表这是一个指针变量
	//2、p是变量,其类型是将p本身删除,剩下的类型就是指针变量的类型。在本例中为:int *
	//3、指针变量p是用来保存什么类型的数据:将指针变量p和离它最近的 * 删除,剩下什么类型就保存什么类型数据的地址。在本例中为:int
	p = &a; //赋值

	system("pause");
	return 0;
}
  1. 指针变量保存谁的地址就指向谁
*p = 100;//在使用时,*与p结合代表 取p指针所指向的那块空间的内容
	//在使用时,对一个表达式取 * ,就相当于对表达式减少一级 * ;如果对表达式取 & ,就相当于加一级 * 

在使用时,对一个表达式取 *,就相当于对表达式减少一级 * ;如果对表达式取 & ,就相当于加一级 *

  1. 指针变量的大小

无论是哪种类型指针,其大小只和编译器有关。

int main()
{
	char *p1;
	short *p2;
	int *p3;
	int **p4; //p4也是一个指针变量,它的类型为 int **

	printf("%d\n", sizeof(p1));//大小:4
	printf("%d\n", sizeof(p2));//大小:4
	printf("%d\n", sizeof(p3));//大小:4
	printf("%d\n", sizeof(p4));//大小:4

	system("pause");
	return 0;
}
  1. 指针的宽度和步长
    指针宽度(步长) = sizeof(指针类型) 例如:sizeof(int *); sizeof(int **)等……
    步长:指针加1跨过多少字节
    char *p1 宽度:1字节
    short *p2 宽度:2字节
    int *p3 宽度:4字节
    int **p3 宽度:4字节
int main()
{
	int num = 0x01020304;
	char *p1 = (char *)# //因为num为int 类型,所以对 num 取地址为 int * 类型,因此,需要强转为 short * 类型
	short *p2 = (short *)#
	int *p3 = #

	//通过 * 取指针变量所指向空间的内容时,取的是内存的宽度,和指针变量本身的类型有关 
	printf("%x\n", *p1); //04
	printf("%x\n", *p2); //0304
	printf("%x\n", *p3); //01020304

	//指针地址
	printf("%p\n", p1);
	printf("%p\n", p2);
	printf("%p\n", p3);
	//指针 + 1 的地址
	printf("%p\n", p1 + 1);
	printf("%p\n", p2 + 1);
	printf("%p\n", p3 + 1);

	system("pause");
	return 0;
}
  1. 野指针
int main()//野指针
{
	int *p;//此时 指针 p 的指向未定义,属于野指针,其指向是随机的,不可以操作野指针,运行会出错
	*p = 2000; 
	printf("%d\n", *p);//err

	int a = 10;
	int *p = &a;//指针 p 指向变量 a 
	*p = 2000; 
	printf("%d\n", *p);//ok


	system("pause");
	return 0;
}
  1. 空指针
    空指针作用:如果使用完指针将指针赋值为NULL,在使用时判断指针是否为NULL,就可以知道其是否被用
int main()//空指针
{
	int a;
	//将指针的值赋值为0,0x00000000 = NULL
	int *p = NULL;
	*p = 200; //err 因为P保存了0x00000000的地址,这个地址是不可以使用的
	printf("%d\n",*p);

	system("pause");
	return 0;
}

  1. 万能指针
    注意:定义 void * 类型, 其类型是4字节(32位系统下),打印的时候,系统并不知道 void类型 取多少字节,因此打印会报错,而定义不会报错
int main()//万能指针 void *
{
	//void b;//err 不可以定义VOID类型的变量,因为编译器不知道给void类型的变量分配多少字节的空间
	
	int a = 10;
	short b = 200;
	//可以定义void * 类型,因为其类型是4字节(32位)
	void *p = (void *)&a; //万能指针可以保存所有类型的地址,但要注意地址库类型转换
	void *q = (void *)&b;
	//printf("%d\n", *p);        //err 因为 P 是 void *,不知道取多少字节
	printf("%d\n", *(int *)p);  //       *( (int *)地址) == 该地址中的内容


	/*注意:定义 void * 类型, 其类型是4字节(32位系统下),打印的时候,系统并不知道 void*类型 取多少字节,因此打印会报错,而定义不会报错*/

	system("pause");
	return 0;
}
  1. const 关键字修饰
    const 修饰变量 a ,意为不能通过a修改a内存中的值,但可以通过指针修改
int main()
{
	//const 修饰变量 a ,意为不能通过a修改a内存中的值,但可以通过指针修改
	const int a = 10;
	int *p = &a;
	*p = 100;
	printf("%d\n", *p); // 100
	printf("a = %d\n", a);// 100



	system("pause");
	return 0;
}
  • const int *p = &a; *不能通过 p 修改其指向内存中的内容,但可以通过变量a本身修改

  • int * const p = &a; p 保存的地址不可以修改

  • const int *const p = &a; *P本身的指向不能修改,也不能通过 p 修改p指向的内存中的内容

int main()
{	
	int a = 10;
	//const 修饰 * ,意为:不能通过 *p 修改其指向内存中的内容,但可以通过变量a本身修改
	const int *p = &a;
	//*p = 100;//err 不能通过 *p 修改其指向内存中的内容,但可以通过变量a本身修改
	a = 100;
	printf("%d\n", *p); // 10
	printf("a = %d\n", a);// 100

	int b = 20;
	//const 修饰 变量p ,意为:p 保存的地址不可以修改
	int * const p = &a; 
	//p = &b;  //err 
	*p = 123;
	printf("%d\n", *p); // 123

	const int *const p = &a;//P本身的指向不能修改,也不能通过 *p 修改p指向的内存中的内容


	system("pause");
	return 0;
}
  1. 多级指针
    定义多级指针时,定义的指针类型只需要比想保存的的数据类型多一个 * 即可
    “ int *p ” :代表这个p是一个指针变量,其中 p 为变量。
    p的类型 :将p删除,剩下的类型
    p保存谁的地址:将 * 和 p 一起删除,剩下的类型,就是其保存的类型。
int main()//多级指针
{
	int a = 10;
	int *p = &a;

	//二级指针 q 保存 一级指针p 的地址,即 q -> p ->a
	int **q = &p;

	// **q == *(p) == a
	// **q == *(p) == *(&a) == a 
	printf("%d\n", **q);//通过二级指针 q,取 变量a 中的值10
	printf("%d\n", *p); //通过一级指针 p,取 变量a 中的值10



	system("pause");
	return 0;
}
  1. 通过指针操作数组
    指针 + 1 :跨一个步长
    要得到内存手中的内容,需要先拿到该内存的地址,再解引用取内容 : *(address)
int main()
{
	//a 是数组名,保存的是首元素的地址  因此 a == &a[0]、 a+1 == &a[0+1] == &a[1]等等
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int *p = a;//指针 p 保存的是数组首元素的地址
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		//printf("%d ", a[i]);//通过数组下标访问数组元素
		printf("%d ", *(p + i));//通过指针访问数组元素
	}

	int b[10] = { 0 };
	for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
	{
		*(p + i) = i;//通过指针给数组赋值
	}
	for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
	{
		printf("%d ", *(p + i)); //0 1 2 3 4 5 6 7 8 9
	}


	system("pause");
	return 0;
}

  1. 指针的运算
    两指针类型一致相减得到中间跨过多少个元素
    两指针相加无意义
int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int *p = a;
	printf("%d\n", *(p + 3));//P+3 表示指针向后移动3个元素,再解引用,表示取当前地址中得内容,因此该语句输出为 4 

	//int *q = &a[9];该语句和下一行语句等价
	int *q = (int *)(&a + 1) - 1; //&a+1 表示跨过整个数组,(&a+1) - 1表示让 指针q 指向数组a的最后一个元素的地址,两边类型需要匹配,所以要进行强转
	
	printf("%d\n", q - p);//两指针相减,代表数组中有多少个元素

	//printf("%d\n", p + q)); //err 在C语言中,了两指针相加无意义

	system("pause");
	return 0;
}
  1. [ ]不是数组的专属
    [ ] <==> *()
int main()// [] 的使用
{
	int a = 10;
	int *p = &a;
	*p = 100;
	printf("%d\n", a);//此时,a = 100

	// []不是数组的专属: [] == *()
	int b[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int *p = b;
	for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
	{
		//下列打印语句都等价
		printf("%d ", b[i]);
		printf("%d ", *(p + i)); 
		printf("%d ", p[i]); // p[i] == *(p + i)
		printf("%d ", *(b + i));//由于 b 代表数组首元素地址,此时使用的方式同 指针p,因此 b[i] == *(b + i)
	}



	system("pause");
	return 0;
}
  1. 指针数组
    整形数组:是一个数组,数组中每个元素都是一个整型变量
    指针数组:是一个数组,数组中每个元素都是一个指针
    注意:一定要注意指针的指向,及其存的值是代表什么!!!
int main()//指针数组
{
	int a = 10;
	int b = 20;
	int c = 30;

	//指针数组,数组中每个元素保存一个地址
	int *num[3] = { &a,&b,&c };
	printf("%d\n", sizeof(num));// 指针数组的大小 = 类型 * 元素个数

	//通过数组打印出每个元素指向内存的内容
	for (int i = 0; i < sizeof(num) / sizeof(num[0]); i++)
	{
		printf("%d ", *num[i]);
	}

	//定义一个指针,保存指针数组num首元素的地址
	//num[0] 为 int * 类型,要保存该类型的地址,需要用到二级指针保存
	int **q = num; //二级指针保存  &num[0] == num == &(int *) == int ** 
	
	printf("%d\n", **q); //**q == a的值,
	//通过 二级指针q 打印出每个元素指向内存的内容
	for (int i = 0; i < sizeof(num) / sizeof(num[0]); i++)
	{
		//  q+1   等价于  &num[1];
		//  *(q+1)  得到num[1]中的内容,即变量b的地址( &b )
		//  *(*(q+1))得到 变量b 的内容
		printf("%d ", *(*(q+i))); 
	}



	system("pause");
	return 0;
}
  1. 指针作为形参
    值传递函数:不会修改实参的值 入参为变量,其实质是局部变量,子函数被调用完之后,局部变量会被释放,因此不会影响到实参的值
    址传递函数:会修改实参的值 入参为地址,表示对改地址中的内容进行操作,即便返回主函数中,该内存中的值已经被改变,因此会改变实参的值
void swap1(int x, int y) //值传递
{
	int temp = x;
	x = y;
	y = temp;
	printf("交换后: x = %d, y = %d\n", x, y);
	return;
}

void swap2(int *x, int *y) //址传递
{
	int temp = *x;
	*x = *y;
	*y = temp;
	printf("交换后: x = %d, y = %d\n", *x, *y);
	return;
}

int main()
{
	int a = 10;
	int b = 20;
	printf("值传递交换函数:\n");
	printf("a = %d, b = %d\n", a, b);
	swap1(a, b);
	
	printf("址传递交换函数:\n");
	printf("a = %d, b = %d\n", a, b);
	swap2(&a, &b);

	system("pause");
	return 0;
}
  1. 数组作为函数的参数
    数组作为函数的参数,在子函数形参列表中会退化为指针
    Eg: void print_arr(int x[10]) ====> void print_arr(int *x)
//void print_arr(int x[10]) 数组作为函数的形参,会退化为指针 void print_arr(int x[10]) ====> void print_arr(int *x)
void print_arr(int *x, int n)
{
	//int n = sizeof(x) / sizeof(x[0]);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", *(x + i));
	}
	printf("\n");
}
int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr(a,10);//打印数组的内容


	system("pause");
	return 0;
}
  1. 指针作为函数返回值
int *getnum1()
{
	
	srand(time(NULL));
	//int num = rand();//num为子函数中的局部变量,该变量在子函数调用完后会被释放
	num = rand();
	return &num;//返回值为地址
}
int num = 0;//全局变量,整个工程都可以使用。程序启动时开辟空间,知道程序结束释放空间
int *getnum2()
{

	srand(time(NULL));
	//int num = rand();//num为子函数中的局部变量,该变量在子函数调用完后会被释放
	num = rand();
	return &num;//返回值为地址
}

int main()
{
	int *p = getnum1();	//子函数中定义的变量num是局部变量,局部变量在子函数调用结束后会释放,因此会造成隐患,不建议这样使用
	int *p = getnum2();	//在函数外定义的变量是全局变量,可以这样使用
	printf("%d\n", *p);

	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值