征服C指针

征服C指针计划(2020.5.23——2020.7.30)

00001、对于指针变量p, 输出&p与输出p的区别
int main(){
	int* p;
	int num = 1;
	printf("p的地址(未赋值):%p\n", &p);
	printf("num的地址:%p\n", &num);
	p = #
	printf("p的地址(赋值):%p\n", &p);
	printf("p的地址(赋值):%p\n", p);
	return 0;
}

运行结果:
在这里插入图片描述
总结:

  1. &p输出的是变量p的地址,p输出的是存放在变量p中的地址;
  2. 在变量p没有赋值之前,无法输出存放在变量p中的地址。
00002 、一招解决关于C指针声明乱象问题

C的声明:类型名 + 变量名

int p;

对于指针变量的命名,第二种书写格式更符合普遍命名规则,即类型名 + 变量名

int *p; 或者 int* p;

但是当命名多个指针变量时,第二种命名方式又存在弊端:

int* p, q;	//q不是指针

所以
命名多个指针时,不要偷懒!

int* p;
int* q;
00003、C语言中关于数组的声明也是一种乱象
int p[5];

数组也是一种类型,但是对于数组的声明不满足:类型名 + 变量名(java中关于数组的声明int[] p相对来说合理一些)。

00004、关于指针中&与*的考虑

对于

int* p = &a;

这个声明,当p指向a的时候,* p的值和int变量a的值是一样的,于是会出现 “ 一旦在p前加上* ,*p就可以和int变量a等同使用了”这种想法,这种思考方式有一定的道理,但是如果写成

int *&a;

这样a作为int型变量来声明,但是编译器会报错。因此,“等同”的想法是不可靠的。

00005、关于int main(void)问题

在C语言标准中,关于main函数的使用只有以下两种形式:

int main(int argc, char *argv[]){...return 0;}

或者

int main(void){...return 0;}

但是经常会出现

void main(void){...}

这种是错误的,不过当然这种形式也可以跑的起来,但是对于部分编译器来说还是会挂上几个警告。所以要避免这种写法。

00006、关于“指针”二字的歧义

令人费解的一件事是,“指针类型”、“指针类型变量”、“指针类型变量的值”,往往都被统称为指针,对于这一点一定要高度警惕。

00007、关于“指针指向什么类型”的疑惑

指针就是地址,指向int型的指针也好,指向double型的指针也好,又有什么区别呢?有必要去区分他们吗?
某种意义上说这种说法有一定的道理。不仅如此,ANSIC还为我们准备了一个“可以指向任何类型的指针类型”——void*类型,如下:

	int number = 5;
	void* p;
	p = &number;
	printf("%d", *(int*)p);

指针p指向number,但是p只有地址没有数据类型,这无法取出number的值,所以这里用的是强制类型转换,先将void类型转换为int类型,再进行取值运算(运行结果是5)。
所以是可以不区分的,但是毕竟写着麻烦的多,因此直接定义指针指向的类型。

顺便扩展一下void*类型的一个用法:

	int number1 = 5;
	double number2 = 1.2;
	void* p;
	void* q;
	p = &number1;
	q = &number2;
	p = q;
	printf("%f", *(double*)p);

注意,没有标明数据类型的指针可以相互赋值,但是最终使用时要回归原数据类型(如强制转换为double型),否则没有意义。
运行结果:
在这里插入图片描述

00008、C语言中的指针运算

指针运算是指针对指针进行的整数加法运算,以及指针之间进行减法运算的功能,C语言的指针运算功能是其他语言所没有的。

	int number;
	int* p;
	p = &number;
	/*输出p的值*/
	printf("%p\n", p);

	/*p的值加1并输出*/
	p++;
	printf("%p\n", p);

	/*p的值加2并输出*/
	p = p + 2;
	printf("%p\n", p);

	/*p的值减3并输出*/
	p = p - 3;
	printf("%p\n", p);

运行结果:
在这里插入图片描述
因为指针p指向的数据类型是int型(4个字节),所以加或者减都是4的倍数,关于指针的运算先简单记录一下,随后根据数组与指针的关系对此知识点进行重点分析。

00009、什么是空指针

1、空指针是一个特殊的指针值;
2、空指针是一种可以确保没有指向任何对象的指针,通常使用宏定义NULL来表示控制针的常量值。

00010、空指针的作用

1、独特性:因为空指针和任何非空指针相比都不相等,所以常用作函数发生异常时的返回值使用(不冲突);
2、安全性:如今大部分的操作系统,一旦发现空指针引用对象会立即将其强行终止,因此将NULL初始化指针,可以有效避免对指针的错误使用(DOS(无内存保护机制)和UNIX操作系统支持空指针引用对象);
3、边界性:在链表中经常在末尾放一个空指针,代表“请注意,后边已经没有元素了”。
在这里插入图片描述

00011、NULL、0 和 '\0’的使用乱象

1、对于字符串,结尾要用’\0’, 对于使用NULL来结束字符串的用法,虽然在某些环境下可以跑起来,但是从根本上是一种错误(出错将会是必然的);
2、’\0’和0有什么关系呢?
(1)C语言标准中定义“所有的位为0的字节称为空字符”,也就是说,空字符是值为0的字符;
(2)空字符在表现上通常使用’\0’,但因为’\0’是常量,所以在实际上它等于0;

	char p = '\0';
	printf("%d",p);

在这里插入图片描述
3、NULL与0又有什么关系?
(1)在大多数环境中,NULL就是数值为0的地址,但是由于硬件限制等原因,世上也存在地址不是0的空指针;
这里有两种初始化:
1>malloc()函数和calloc()函数,是C语言提供的两种动态分配函数,前者不会被初始化,后者存在初始化;
2>对于一个结构体,很多人往往会使用memset()将内存区域清零再使用;
以上初始化和清零,其实就是单纯的使用0来填充位,通过这种操作,如果被初始化和清零的区域存在指针,这个指针能不能当做空指针来用,最终还得取决于运行环境。
这种依赖于运行环境的代码,质量当然是不高的,所以为了提高质量,通常在代码前边加上一句:

#define NULL 0

将0当做空指针来使用,除了极特殊的情况,通常是不会发生错误的。
(2)在C语言中,“当常量0处于应该作为指针使用的上下文中时﹐它就作为空指针使用”。

int*p = 0;

按道理说0和2是没有区别的,都是int型的量,但是int* p = 2肯定是不正确的,那么等于0为什么正确呢?因为0的位置符合“作为指针使用的上下文”,它要赋值给一个指针,所以就作为空指针使用了。
这就意味着,对于某些硬件限制的而导致地址不为0的空指针,也不能对将NULL的值#define成其他值。

00012、指针经典入门swap函数
#include<stdio.h>
int main() {
	void swap(int* p, int* q);

	int a = 1, b = 2;

	printf("a = %d, b = %d\n", a, b);
	swap(&a, &b);
	printf("a = %d, b = %d\n", a, b);
}

void swap(int* p, int* q) {
	int temp;
	temp = *p;
	*p = *q;
	*q = temp;
}

由于swap使用了*运算,所以通过指针可以间接地访问到a和b,向swap传递的是地址,a和b本身并没有变。
当然,如果单纯为了调换两个整数,也可以用这么一句话:

#define SWAP(a, b) (a += b, b = a - b, a -= b)

但是如果a, b是同一个参数的话,这个宏定义是不能运行的,比如SWAP(a[i], a[j]),一旦i==j,那么这种方式是肯定会出错

00013、“差一问题”,数组为什么要从0开始编号

(1)我想要爬到五楼,如果每爬一层需要10秒,那么爬到五层需要多少秒?如果从0开始计数,4*10 = 40秒
(2)2000年属于20世纪还是21世纪?如果从0开始计数,这个问题很明了(2000~2999)
(3)在二维数组中怎么寻找某一个元素?array[line * width + col]
从0开始计数可以解决很多现实问题,因此要适应“差一问题”,学会从0开始编号。

00014、用指针遍历数组——指针与数组的微秒关系(1)
void ArrayAndPointer(void) {
	void method_1(int array[5]);		//declaration
	void method_2(int array[5]);		//declaration

	int array[5] = { 1,2,3,4,5 };
	method_1(array);
	printf("\n");
	method_2(array);
}

void method_1(int array[5]) {
	int* p = &array[0];
	for (int i = 0; i < 5; i++) {
		printf("%4d", *(p + i));
	}
}

void method_2(int array[5]) {
	int* p;
	for (p = &array[0]; p != &array[5]; p++) {
		printf("%4d", *p);
	}
}

运行结果:
在这里插入图片描述
在method_1中指针本身没有动,只有在输出的时候进行加i处理,method_2中指针随着自加开始移动,虽然实际上并不存在array[5],但是这里并不影响使用,当然也可以写为:p <&array[5], 是可以的。

00015、下标运算符和数组是没有关系的——指针与数组的微妙关系(2)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值