由浅至深->C语言中指针及数组的经典问题分析(一)

引言:该系列第四篇文章。指针、数组是C语言中的重要内容,也是C语言学习者的痛点和难点,本文将尽力去说明其中的陷阱和槽点。

文章向导

  • 指针的本质与总结
  • 数组的本质与总结

一、指针的本质与总结

1.何为指针?

     ~~~~          ~~~    指针是一个其数值为地址的变量,就如 char 类型的变量用字符作为其数值,而 int 型变量的数值是整数。既然指针也是变量,那么其自身也应该被分配有地址,这与指针所指向的地址不同。

2.与指针相关的运算符
1)在指针声明时,*号表示所声明的变量为指针
2)在指针使用时,*号表示取指针所指向的内存空间中的值
3)&运算符后跟一个变量名时,给出该变量的地址

int i = 0;
int j = 0;

int* p = &i; //指针声明

j = *p; //指针使用

这里写图片描述
  这里可以做一个形象的比喻:*号相当于一把钥匙,通过这把钥匙可以打开内存并读取其中的值。从C语言的描述上来说,p等价于&i,*p等价于i 。

3.指针声明时的小问题

     ~~~~          ~~~    你是否对以下这几种指针声明感到困惑,它们是一样的吗?

int* pi, int * pi, int *pi, int*pi;

     ~~~~          ~~~    事实上星号和指针名之间的空格是可选的,C语言中更倾向于使用 i n t      ∗ p i int^{\,\,\,\,*}pi intpi,以强调 ∗ p i ^* pi pi是一个int类型的值。C++中则倾向于使用 i n t ∗ p i int^* pi intpi,以强调 i n t ∗ int^* int是一种类型——即指向int的指针。但实际上在哪里添加空格对于编译器来说没有实质性的区别,即你也可以写成int*pi;

4.指针占用的内存大小

#include <stdio.h>
int main()
{
	int i = 0;
	int* pI;
	char* pC;
	float* pF;
	
	pI = &i;
	*pI = 10;
	printf("%p, %p, %d\n", pI, &i, i);
	printf("%d, %d, %p\n", sizeof(int*), sizeof(pI), &pI);
	printf("%d, %d, %p\n", sizeof(char*), sizeof(pC), &pC);
	printf("%d, %d, %p\n", sizeof(float*), sizeof(pF), &pF);
	
	return 0}

     ~~~~          ~~~    上述程序的运行结果如下(32位VC++6.0编译平台):
这里写图片描述
  此处指针所占用的内存大小,取决于系统位数。32位系统,结果为4;64位系统,结果为8.(牢记指针值是地址,而地址值与系统位数有关)

5.可以把指针看作是整数类型吗?

     ~~~~          ~~~    在大多数计算机系统内部,地址由一个无符号整数表示,但这并不意味着可把指针看作是整数类型。比如下面这个例子:

#include <stdio.h>
int main()
{	
	float f = 8.25;

	unsigned int * p = (unsigned int*)&f;
	//地址在大多数系统内部由无符号整数表示,此处的*表示该变量为一指针。
	//右边若改为(unsigned int)&f则会出现警告:初始化时将整数赋给指针,未作类型转换。这是因为C语言中并不会自动地做强制类型转换,两边运算类型要匹配才行。
	
	//08代表最小字段宽度为8,X代表使用十六进制数字0F的无符号十六进制整数。
	printf(0x%08X\n”,*p); //0x41040000,实际上由于指针类型不匹配,无法使用%f打印出8.25
	printf(0x%08X, 0x%08X\n”,p, &f); //0x0070FE4C 0x0070FE4C
	printf(0x%08X\n”,&p);  //0x0070FE40
	
	return 0;
}
	

     ~~~~          ~~~    由上面这个例子可知:指针的确是一种新的数据类型,而并非一种整数类型。

6.传值调用和传址调用

  • 函数调用时实参值将复制到形参(传值调用
  • 当一个函数体内部需要改变实参的值,则需要使用指针参数(传址调用
/*传址调用实例*/
#include <stdio.h>
int swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
int main()
{
	int aa = 1;
	int bb = 2;
	printf("aa = %d, bb = %d\n", aa, bb); // 1 2
	swap(&aa, &bb); //传址调用
	printf("aa = %d, bb = %d\n", aa, bb); // 2 1
	return 0;
}

7.常量与指针

  • 几种指针的声明形式
const int* p;//p可变,p指向的内容不可变—指针常量(指向常量的指针)
int const* p;//p可变,p指向的内容不可变—指针常量
int* const p;//p不可变,p指向的内容可变—常量指针
const int* const p;//p自身和p指向的内容都不可变
//这里的不可变仅仅指的是不能通过p本身来改变,不考虑其他手段。
  • 一种名为“左数右指”的记忆方法
    -当 const 出现在星号左边时指针指向的数据为常量
    -当 const 出现在星号右边时指针本身为常量
  • 实例验证
#include <stdio.h>
int main()
{
	int i = 0;
	const int* p1 = &i;
	int const* p2 = &i;
	int* const p3 = &i;
	const int* const p4 = &i;
	
	*p1 = 1; // compile error
	 p1 = NULL; // ok
	 
	*p2 = 2; // compile error
	 p2 = NULL; // ok
	 
	*p3 = 3; // ok
	 p3 = NULL; // compile error
	 
	*p4 = 4; // compile error
	 p4 = NULL; // compile error
	 
	return 0;
}

二、数组的本质与总结

1.何为数组(array)?

     ~~~~          ~~~    数组是由一组类型相同的元素所组成的有序集合,比如声明一个int[5]的整型数组,书写简单但其内涵却可深入挖掘。具体如下图所示:
这里写图片描述

2.数组的大小与初始化

  • 初始化时应注意的细节
int a[5] = {1,2,3,4,5}; //显示指定数组元素的个数
int b[] = {1,2}; //隐式指定
int c[5] = {0}; //第一个元素为 0,其余未指定的编译器默认初始化为 0,故全为0
  • 如何自动计算数组元素的个数

     ~~~~          ~~~    假定有一int型数组a,可通过下面的算式快速得出数组元素个数。

int a[5] = {0};

printf("count for a : %d\n", sizeof(a)/sizeof(int));

3.数组首元素地址与数组地址

1)需要明确的几点

  • 数组名代表数组首元素的地址
  • 数组的地址需要用取地址符&才能得到
  • 数组首元素的地址与数组的地址,两者在值上相同
  • 数组首元素的地址与数组的地址是两个不同的概念(即虽然两者在值上是相等的,但所指示的数据长度不同:前者为第一个数据的长度,后者为整个数组的数据长度)。

为了说明上述最后一条的观点,不妨从如下的公式着手来理解:
a + 1 ⇔ ( u n si g n e d    i n t ) a    +    s i z e o f ( ∗ a ) ⇔ ( u n si g n e d    i n t ) a    +    s i z e o f ( a [ 0 ] ) & a + 1 ⇔ ( u n si g n e d    i n t ) ( & a )    +    s i z e o f ( ∗ ( & a ) ) ⇔ ( u n si g n e d    i n t ) ( & a ) +    s i z e o f ( a ) a+1\Leftrightarrow \left( un\text{si}gned\,\,int \right) a\,\,+\,\,sizeof\left( *a \right) \Leftrightarrow \left( un\text{si}gned\,\,int \right) a\,\,+\,\,sizeof\left( a\left[ 0 \right] \right) \\ \&a+1\Leftrightarrow \left( un\text{si}gned\,\,int \right) \left( \&a \right) \,\,+\,\,sizeof\left( *\left( \&a \right) \right) \Leftrightarrow \left( un\text{si}gned\,\,int \right) \left( \&a \right) +\,\,sizeof\left( a \right) a+1(unsignedint)a+sizeof(a)(unsignedint)a+sizeof(a[0])&a+1(unsignedint)(&a)+sizeof((&a))(unsignedint)(&a)+sizeof(a)

2)实例验证

/*三者值一样,但意义却不相同*/
#include <stdio.h>
int main()
{
	int a[5] = { 0 };
	printf("a = %p\n", a); 
	printf("&a = %p\n", &a); 
	printf("&a[0] = %p\n", &a[0]); 
	return 0;
}

4.数组名的盲点知识

  • 数组名可以视为为常量指针(本质上数组与指针是不同的事物)
  • 数组名“指向”的是内存中数组首元素的起始位置
  • 数组名不包含数组的长度信息
  • 在表达式中数组名只能作为右值使用
  • 当数组名作为sizeof操作符的参数或&运算符的参数时,不能视为常量指针
/*32位机上测试结果*/
#include <stdio.h>
int main()
{
	int a[5] = {0};
	int b[2];
	int* p = NULL;
	
	p = a;
	printf("a = %p\n", a);
	printf("p = %p\n", p);
	printf("&p = %p\n", &p);
	
	printf("sizeof(a) = %d\n", sizeof(a)); //20
	printf("sizeof(p) = %d\n", sizeof(p)); //4
	printf("\n");
	
	p = b;
	printf("b = %p\n", b);
	printf("p = %p\n", p);
	printf("&p = %p\n", &p);
	
	printf("sizeof(b) = %d\n", sizeof(b)); //8
	printf("sizeof(p) = %d\n", sizeof(p));//4
	
	b = a; //error
	
	return 0;
}

写在最后:关于数组与指针之间更为深入的关联,本文并未详细谈及,若读者还有疑问,可见由浅至深->C语言中指针及数组的经典问题分析(二)这篇文章的阐述与分析。

参阅资料
C Primer Plus(第五版)
Primer C++ 第五版
狄泰软件学院-C进阶剖析教程
高质量嵌入式Linux C编程

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值