我应该比大多数读者年龄都要大一些,所以我就自称”谱哥“,做事靠谱,为人靠谱的意思;针对 C 语言三大核心:数组、指针、函数,今天继续写技术文章。
上次 C 语言写到了数组,有些书是先讲指针,有些书是先讲函数,按照我以前学习 C 语言的顺序,以及对 C 语言的理解,学习的顺序是这样的:数组--->指针--->函数,所以本篇文章讲解 C 之指针。
C 语言是值得好好学习的一门语言,是一门基础语言,更是我编程入门的语言,其中很多编程思想,至今影响着我,在工作中对我的帮助很大。
基本概念
学习 C 语言之指针,必须强烈推荐一本书:《C 和指针》,好好看,把这本书吃透,C 指针就差不多了。
1、
指针有两个要素
(1)、首地址:内存中多个连续字节的第一个字节的编号;在 32 位系统下,每个字节的编号都是 32 位二进制,也就是 4B,任何类型的指针都只占 4B 的存储空间。
1char *a; char* a;
2short *b; short* b;
3int *c; int* c;
4double *d; double* d;
5
6printf("%d %d %d %d\n", sizeof(a), sizeof(b), sizeof(c), sizeof(d));
7//32位系统下,结果为:4 4 4 4,证明了只要是指针,都是 4B,其实指针的值就是内存中一个字节的地址,仅仅是代表了一个编号而已
(2)、指针所指向空间的数据类型:以指针的值为首地址,其所指向的空间的数据类型。
2、
指针类与值类
我们之前学习过的各种数据类型:int、float 等等都统称为值类。
指针类和值类不能简单的适用以前所讲述的语法,例如:自动类型转换和强制类型转换是不适用指针类的。
3、
指针的基本运算
& 和 *
A、
&:取地址运算符,单目运算符,优先级在单目运算符中比较低,低于 ++、--。
&左值(左值只能是空间、变量),取的是内存的地址。
&常量和&表达式是语法错误的。
1 &3、&(&n)、&&n,都是错误的形式
B、
1short i, j, *p, *q, **r;
2//两个值类变量:i 和 j;三个指针类变量:p、q、r
3//p、q 是指针变量,其指向的数据类型为:short;
4//r 是指针变量,但是 r 所指的空间的数据类型是 short *
5//对于指针所指向的数据类型:去掉最后一个"*",剩余部分与类型所组成的内容就是了
6
7//p = &i; 是正确的,把变量/空间 i 的地址赋值给 p
8//r = &i; 是错误的,必须保证左边和右边的数据类型是一样的,指针才可以进行赋值操作,r 的数据类型是:short **,&i 的数据类型是:short *,所以赋值失败
9//r = &p; 是正确的,r 指向 p,必须首先的明确这些指针的数据类型的对应关系,以及指向,指向空间的数据类型
C、
* 运算符
单目运算符,与 & 优先级相同,且和 & 互为逆运算(连续的两个 & 和 * 运算符在一起,相互抵消),例如:*&n <=> n、&*p <=> p。
语法:*指针(常量、变量、表达式)
1short i, j, *p, *q, **r;
2p = &i;
3q = &j;
4*p = 30; //把 30 赋值给”p 所指向的空间“,也就是 i,等价 i = 30
5*q = *p + 15; //将 p 所指向空间的值,和 15 求和,把和值赋值给 q 所指向的空间,等价 j = i + 15
*某的理解:
(1)、其在左边,理解为:某所指向的空间;
(2)、其在表达式中,理解为:某所指向的空间的值。
1short i, j, *p, *q, **r;
2p = &i;
3q = &j;
4r = &p;
5*p = **r + *q; //将 r 所指向空间的值所指向空间的值和 q 所指向空间的值求和,赋值给 p 所指向的空间,等价 i = i + j;
4、
指针定义的理解
A、
1char i, j, *p = &i, *q = &j, **r = &p;
2//定义语句中的 *,不是指向运算符,它只是指针身份的声明
B、
double *p;
*p = 3.14;
对于上述语句的理解,是至关重要的。
上述语句会引起:运行时致命错误!!!
变量 p 定义为指针变量,占用 4B,但是没有初始化(没有赋初值),其值为垃圾数据;
*p = 3.14; 的意思是:将 3.14 赋值给 p 所指向的空间,也就是将 3.14 赋值给“以 p 的值作为首地址”,该首地址所指向的空间;
综上所述:将 3.14 赋值给以垃圾值为首地址,所指向的空间,这个空间在哪里,只有鬼知道!这个垃圾值的取值范围在 0 到 40 亿之间,若落在当前软件申请的空间范围内,则相安无事;否则,这个操作(*p = 3.14;),将对不属于本软件所申请的空间进行操作,被操作系统认为是“非法访问”,操作系统将强行终止这个软件的执行!
5、
指针的其他运算(主要是指针的加减运算)
指针加/减整型值,其结果是一个指针,且指向空间的类型不变。
A、
指针加/减整型
P + 1:所得到的还是指针,将会指向 P 所指向空间的下一个地址(到下一个地址移动的长度是指向空间数据类型的长度)。
指针 +1 所得到的“字节编号”的值。与指针原值(原字节编号)相差sizeof(所指向空间的数据类型)。
指针 +n 的值与指针原值相差 n 倍的 sizeof(所指向空间的数据类型)
B、
指针减指针 => int 类型
指针减指针,其结果的绝对值是:两指针所指向的空间之间 sizeof(数据类型) 元素的个数。
参加指针相减运算的两个指针,其指向空间的数据类型必须一致!
实质上,指针相减的内部运算过程是:两指针值(字节编号)相减,再除以 sizeof(所指向空间的数据类型)
指针不能加指针,会出现语法错误!
总结:指针 + 1 或者 -1,指针移动的步长:是指向空间数据类型的长度(这个长度是通过 sizeof() 可以计算的)。
指针与数组
1、
指针与一维数组
int a[10];
数组名称的本质:是该数组的首地址常量。
A、数组名称是常量
1int a[10], *p = &a[0];
2++a; //错误!++运算的本质是赋值;不能对常量赋值;a 是数组名称,是常量
3++p; //指针变量
B、
数组名称是首地址(指针),可以参加指针能够参与的所有运算。
1*a = 3; //a 的值本质上是 a[0] 的首地址,因此可以说:a 指向 a[0]
2*(a+0) = 3; <=> a[0] = 3;
3*(a+1) = 3; <=> a[1] = 3;
4*(a+2) = 3; <=> a[2] = 3;
int i; //且其取值在有效下标范围内:
*(a+i) = 3; <=> a[i] = 3;
得到了指针与数组的本质:
a[i] <=> *(a+i)
表象 本质
2、
指针与二维数组
指针与二维数组在理解上是比较难的,只有理解了指针与二维数组,那么指针与多维数组,三阶指针、四阶指针、五阶指针、直至多阶指针,一维数组,二维数组,三维数组,直至多维数组,在理解上就都不是问题了,挖掘本质,才能更清楚的认识。
指针与二维数组属于指针的高级进阶,后面有时间在分享这块的知识点。
指针与字符串
1、
"ABCDE" //字符串常量
字符串常量的本质:是该字符串的首地址常量,即指针常量。
1char *p = "I love you";
2
3p = "Hello";
4printf("%s\n", p);
5p[2] = 'm'; //错误,字符串常量是不能进行赋值操作的
2、
字符串常量的本质是指针常量,可以参加指针所能够参加的所有运算。
因此:
字符串常量可以进行 * 和 [] 的运算;
字符串常量不能相加;(指针是不能相加的);
字符串常量的关系运算符(大小比较),实质上比较的是它们的内存首地址的大小比较,而非字符串内容(ASCII码)的大小比较。
字符串常量不能更改其内容。
字符串的本质是字符数组,数组名称的本质依然是指针常量;
所以,以上结论依然适用于字符串!
3、
字符串类处理函数
A、
string.h
对于 scanf("%s"...) 和 gets() 的理解
strlen() 函数的工作原理:
C 语言将其唯一的参数当成首地址,从这个首地址所指向的空间开始,统计所有字符的个数,直到遇到0(结束标志)。
B、
字符串输入、输出函数
输入:scanf("%s", ...)、gets(...)
输出:printf("%s", ...)、puts(...)
C、
strcpy()
声明:char *strcpy(char *target, char *source);
功能:将后者字符串,赋值给前者:将以第二个参数的值为首地址所指向的字节开始的“遇零则止”的字符,复制一份,赋值给第一个参数的值为首地址所指向的字节开始向后的连续存储空间中;该函数的返回值,就是第一个参数的值。
D、
strcat()
声明:char *strcat(char *, char *);
功能:字符串连接,将第二个参数所指向的字符串,连接到第一个参数所指字符串的末尾。
E、
strstr()/strchr()
声明:char *strstr(char *string, char *subString);
//subString意思为:子串
功能:查找 subString 第一次出现的地址值,若 subString 不是 string 的子串,则返回 NULL。
F、
strcmp()
声明:int strcmp(char *s1, char *s2);
功能:比较 s1 和 s2 所指向的字符串的内容;
若 s1 字符串内容小于 s2 字符串内容,则返回值为负整数;
若 s1 字符串内容大于 s2 字符串内容,则返回值为正整数;
若二者内容相等,则返回为0。
G、
strrev()
声明:char *strrev(char *);
功能:将字符串逆序。
这篇文章仅仅是 C 语言指针的入门篇,看完之后,不知道大家能理解多少,学到多少;根据这篇文章的线路,去学习 C 指针,会清晰很多,只有多思考,才能消化吸收,才能理解。