《C和指针》读书笔记(第六章 指针)

有人称指针是C语言的灵魂,有人又称指针说指针是C语言最难的部分,那么,指针对C语言来说到底意味着什么?指针到底是不是C语言和C++经久不衰的原因之一呢?

指针的问题不是三言两语就能表达清楚的,而在《C和指针》第六章中也仅仅是提到了指针有关的部分内容,指针的精彩与巧妙之处,还需要在后续的章节和具体的项目代码中细细体会。先来看看本章内容的基本框架(思维导图)。

思维导图

在这里插入图片描述
从思维导图里可以看出,关于指针的内容还是比较多的,我们的重点需要放在指针表达式的理解上。

6.1 内存和地址

内存和地址是一个很深刻的话题,为了方便地访问计算机的内存数据,我们会对其进行编号,就像现实中我们邮寄地址的邮政编码一样。书中提到,我们只对两件事情感兴趣。

  1. 内存中的每个位置有一个独一无二的地址标识。
  2. 内存中的每个位置都包含一个值。

6.2 值和类型

在现实中我们需要保存各种各样的数值,然而对于计算机的内存来说,只有01,所以某内存中的到底表示什么值,则需要看被解释为什么值。

	int a = 112, b = -1;
	float c = 3.14;
	int *d = &a;
	float *e = &c;

然而在内存中,确有着如下的数值。
在这里插入图片描述
所以书中特别强调:不能简单地通过检查一个值的位来判断它的类型

6.3 指针变量的内容

6.2小节的内容可以看出,d和e中存储的值恰好差8,也就是两个int型变量的距离(因为一个int型变量一般占4个字节),那么变量de的值到底是多少呢?究竟是其本身的值呢?还是其指向内存地址内所保存的值呢?

按照规定,变量的值就是分配给变量的内存位置所存储的数值,即使是指针变量也不例外。从这个规定来看,变量de的值应该分别是100108

6.4 间接访问操作符

通过一个指针访问它所指向的地址的过程称为间接访问或解引用指针。这个用于执行间接访问的操作符是单目操作符*。下面还是通过以上的一个变量看看间接访问操作符如何访问所需的数值。

int main()
{
    
    
	int a = 112, b = -1;
	float c = 3.14;
	int *d = &a;
	float *e = &c;
	printf("a = %d\n", *d);
	printf("c = %f\n", *e);
	system("pause");
}

我们来看看打印输出:
在这里插入图片描述
确实获取到了我们需要的数值。

6.5 未初始化和非法的指针

书中有这样的例子:

	int *a;
	*a = 12;

显然这样的表达是有问题的,从程序中来看,a指向某地址的值为12,但是究竟12位于哪个地址是未知的,所以可以写成下面的形式。

	int num = 12;
	int *a = #
	printf("结果为:%d\n", *a);
	system("pause");
	return 0;

虽然我们并不能直接知道num究竟保存在什么地方,但是可以通过指针a间接访问num中存储的值。

6.6 NULL指针

NULL指针相当于是指针初始化的一种方式,表示当前指针并未指向任何内存地址,以为有时候编程的时候会遇到,需要定义一个指针,但是暂时并未想好要指向的内存地址,这个时候NULL指针就有了用武之地。举个例子。

剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。
s只包含小写字母。

typedef struct hash
{
    
    
    int key;
    int val;
    UT_hash_handle hh;
}hash;
char firstUniqChar(char* s) {
    
    
    int len = strlen(s);
    hash *f = NULL;
    for(int i = 0; i < len; i++)
    {
    
    
        int ele = s[i];
        hash *temp = NULL;
        HASH_FIND_INT(f,&ele,temp);
        if(temp == NULL)
        {
    
    
            temp = malloc(sizeof(hash));
            temp->key = ele;
            temp->val = 1;
            HASH_ADD_INT(f,key,temp);
        }
        else
            temp->val++;
    }
    for(int i = 0; i < len; i++)
    {
    
    
        int ele = s[i];
        hash *temp = NULL;
        HASH_FIND_INT(f,&ele,temp);
        if(temp != NULL && temp->val == 1)
        {
    
    
            return temp->key;
        }
    }
    return ' ';
}

创建了一个哈希表。用来存放字符数组s中出现的各个字母及其出现的频率,在刚刚定义的时候,还没有开始统计,所以不是很好确定到底要指向何处,因此出现了下面的表达式。

hash *f = NULL;

6.7 指针、间接访问和左值

当指针变量作为左值的时候,会出现什么样的情况呢?我们一起来看看书上给的例子(有改动)。

	int a = 4;
	int *d = &a;
	*d = 10 - *d;
	printf("结果为:%d\n", *d);
	system("pause");
	return 0;

打印输出如下:
在这里插入图片描述
可以看出在语句*d = 10 - *d中,该语句可以这样理解:用10减去指针d所指向地址的值,并保存在原地址。

6.8 指针、间接访问和变量

书中提到了这样的运算:*&细品之下好像并没有什么作用,因为先进行了取地址运算,再间接访问。举个例子:

	int a = 4;
	*&a = 40;
	printf("结果为:%d\n", a);
	system("pause");
	return 0;

打印输出:
在这里插入图片描述
可以看出,这与直接对变量a进行赋值并没有什么不同。

6.9 指针常量

关于指针常量的定义(还有指针常量常量指针的区别),我们在此前的章节中介绍过,具体见:

第3章 数据

书中介绍了这样一种情况,如果我们想在指定位置存储指定的值,应该怎么办呢?比方说在地址100的地方存储25,难道我们直接像下面这样:

*100 = 25;

其实这是错误的做法,因为间接访问操作符只能作用于指针,而100是具体的数值,所以就会出现问题,正确的做法应该是下面这样,使用强制类型转换。

*(int *)100 = 25;

虽然书上是这样介绍的,但实际在VS的集成开发环境中会报错,可能在其他环境中不会报错。因为我们直接编辑的内存地址有可能是系统不允许访问的地址,等等。这个表达式必须要知道,因为很多公司的面试题中会考到

6.10 指针的指针

听起来有点绕,但是这种操作(思想)在C语言的编程中非常常见。书上有个例子:

	int a = 12;
	int *b = &a;
	int **c = &b;
	printf("结果为:%d\n", **c);
	system("pause");
	return 0;

打印输出:
在这里插入图片描述
a的地址保存在了b中,然后再将b的地址保存在了c中,这样的操作看着很无聊,但是在后面的章节中就会发现其中的奥妙

6.11 指针表达式

这部分内容非常非常重要!!! 因为全都是常见的指针表达式,这部分内容对理解指针有相当大的帮助。
特别提示:
所谓的作为左值,可理解为往该变量写入数值,
作为的作为右值,可理解为从该变量读出数值。
首先我们对chcp作了以下声明:

	char ch = 'a';
	char *cp = &cp;

总结了书上提到的所有情况,并作了相应的解释(好辛苦啊啊啊啊啊啊啊啊)。
其中的虚线框表示中间的计算结果,实线框表示得到的结果或要写入的地址空间。
在这里插入图片描述

6.12 实例

书上给出了一个求字符串长度的函数,其实这个函数本身库里面也有。

size_t strlen(char *string)
{
    
    
	int length = 0;
	while (*string++ != '\0')
		length++;
	return length;
}
int main()
{
    
    
	int res;
	char a[] = "qwer";
	res = strlen(a);
	if (res)
		printf("The string length is %d",res);
	else
		printf("The string length is zero!");
	system("pause");
	return 0;
}

毫无疑问地,打印输出如下。
在这里插入图片描述
这个例子很好理解,直接将字符串作为实参传入strlen()函数中,函数内部进行判断,如果没到字符串结尾,就将长度值加一,并向后移动一个元素的距离,直到访问完每个元素,然后返回字符串长度即可。

6.13 指针运算

6.13.1 算术运算

  1. 指针 ± 整数
    很显然,这个表达式的含义就是指针往右移动N个连续的存储空间,一般只对数组有用。
    比方说下面这个例子:
int main()
{
    
    
	char ch = 'a';
	char *cp = &ch;
	int a[] = {
    
    0,1,2,3,4,5,6};
	int *p = a;
	printf("%d", *p + 3);
	system("pause");
	return 0;
}

打印输出:
在这里插入图片描述

  1. 指针 - 指针
    指针的减法肯定很好理解,字面意思就是两个指针的距离,事实上也的确如此,减法运算的值是两个指针在内存中的距离,这里肯定指的也是连续的存储空间,一般指的是数组,否则做减法也没有意义。看一个数组的例子:
int main()
{
    
    
	char ch = 'a';
	char *cp = &ch;
	int a[] = {
    
    0,1,2,3,4,5,6};
	int *p1 = &a[0];
	int *p2 = &a[3];
	printf("%d", p2 - p1);
	system("pause");
	return 0;
}

打印输出:
在这里插入图片描述

6.13.2 关系运算

指针之间也可以进行部分关系运算,可以执行的关系运算有<<=>>=
举个例子:

int main()
{
    
    
	char ch = 'a';
	char *cp = &ch;
	int a[] = {
    
    0,1,2,3,4,5,6};
	int *vp = NULL;
	for(vp = &a[0]; vp <= &a[6]; vp++)
		printf("%d\n", *vp);
	system("pause");
	return 0;
}

打印输出如下:
在这里插入图片描述
其实这里的指针充当了迭代器的角色,将数组中的数值一个个地取出来。

---------------------------------------------------------------------------------END------------------------------------------------------------------------------

猜你喜欢

转载自blog.csdn.net/weixin_43719763/article/details/128064859