征服C指针计划(2020.5.23——2020.7.30)
文章目录
- 00001、对于指针变量p, 输出&p与输出p的区别
- 00002 、一招解决关于C指针声明乱象问题
- 00003、C语言中关于数组的声明也是一种乱象
- 00004、关于指针中&与*的考虑
- 00005、关于int main(void)问题
- 00006、关于“指针”二字的歧义
- 00007、关于“指针指向什么类型”的疑惑
- 00008、C语言中的指针运算
- 00009、什么是空指针
- 00010、空指针的作用
- 00011、NULL、0 和 '\0'的使用乱象
- 00012、指针经典入门swap函数
- 00013、“差一问题”,数组为什么要从0开始编号
- 00014、用指针遍历数组——指针与数组的微秒关系(1)
- 00015、下标运算符和数组是没有关系的——指针与数组的微妙关系(2)
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;
}
运行结果:
总结:
- &p输出的是变量p的地址,p输出的是存放在变量p中的地址;
- 在变量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], 是可以的。