详解C语言指针(1)

目录

一、指针是什么 (引入指针概念)

1.指针概念

2.指针变量指向的是什么

3.地址码(指针变量的大小)

总结

二、指针和指针类型

1.指针的解引用操作

2.指针+-运算

三、野指针问题

1.野指针的成因

a.指针未初始化

b.数组越界访问

c.指针指向的空间被释放了

2.如何避免野指针

四、指针的运算

1.指针的加减常数运算

2.指针 - 指针

3.指针的关系运算

五、指针和数组

一、指针是什么 (引入指针概念)

1.指针概念

指针理解有两个要点

1.指针是内存中的一个最小单元的编号,也就是地址

2.平时口语中说的指针,通常指的是指针变量用来存放内存地址的变量

那么什么是指针变量呢,我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。我们可以通过下面的代码进行进一步的理解。

2.指针变量指向的是什么

#include
int main()
{
    int a = 10;
    int *p = &a; 
    return 0;
}

int a = 10相当于在内存中开辟了一块空间,而内存中的每一块存储空间又有相应的地址,我们对变量a取出它的地址,可以使用&操作符,其中a变量占用4个字节的空间,这里是将a的四个字节中的第一个字节的地址存放在变量p中,p就是一个指针变量。下面通过画图可以更好的理解

详解C语言指针(1)_第1张图片

     其中数据在内存中存放都有自己的编号而a为int类型的变量,占据4个字节,蓝色区域存储的就是a的内容,而每个空间都有自己的地址,a变量占用4个字节的空间其中第一个字节的地址会赋给指针变量p。

3.地址码(指针变量的大小)

      那么地址码是怎么来的?电脑中存在着若干条电线,其中存在着一种地址线,一旦通电会产生两种电平一种高电平,一种是低电平,我们假设高电平代表着二进制中的1,低电平代表着二进制中的0。现在的机器通常为32位机器或者64位机器。我们以32位机器为例,相当于有着32位地址线,会产生出32种不同的电信号,组成相应的二进制序列。最小的为00000000 00000000 00000000 00000000,最大的为11111111 11111111 11111111 11111111共有^{_{}}2^{32}种二进制序列。

      我们又知道每个地址标识一个字节那我们根据换算就可以得到,2^{32}个字节就等于4GB的大小空间,同理我们可以得到64位机器的存储空间。这里我们就可以明白

\bullet 在32位的机器上,地址是32个0或者1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节

\bullet 在32位的机器上,地址是64个0或者1组成的二进制序列,那地址就得用8个字节的空间来存储,所以一个指针变量的大小就应该是8个字节

总结

1.内存被划分成一个个的内存单元,每个内存单元的大小是一个字节

2.每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中称为指针

3.地址要存储的话,存放在指针变量中

4.美格内存单元都有一个唯一的地址来标识

二、指针和指针类型

       我们都知道,变量都有不同的类型如整形,浮点型,字符型等等,那么指针有没有类型呢?准确的来讲指针也是有类型的。例如字符型指针,整形指针,浮点型指针。但是我们通过下面的代码会发现无论是什么类型的指针在32位的环境下指针变量的大小都是4个字节。那么问题来了,为什么存在这么多类型的指针呢。都是同样的大小为什么要有这么多指针类型?下面的内容将详细讲解为什么有不同类型的指针变量。

详解C语言指针(1)_第2张图片

1.指针的解引用操作

我们先写出下面的代码

int main()
{
	int a = 0x11223344;
	int* p = &a;
	*p = 0;
}

定义一个整形变量a,a中存放了0x112233(其中0x代表着16进制)将a的地址传给了p,我们先将p定义成整形指针类型,通过解引用操作(解引用操作就是获得该地址中存放的数据)来修改a的内容为0,通过下图的调试我们可以发现结果与我们预期的一样。

详解C语言指针(1)_第3张图片

详解C语言指针(1)_第4张图片

a中的内容发生了修改。但是当我将p的类型改为char类型结果还会一样吗,通过下图的调试信息我们发现只修改了一个字节的内容。

详解C语言指针(1)_第5张图片

我们通过对比可以得出结论,对于char*类型的指针我们解引用只能修改1个字节的内容,而对于int*类型的指针进行解引用我们可以修改4个字节的内容。所以得出结论。指针类型决定了解引用操作访问几个字节。

2.指针+-运算

我们在观察下面的代码

int main()
{
	int a = 10;
	char* p1 = &a;
	int* p2 = &a;
	printf("%p\n", p1);
	printf("%p\n", p1 + 1);
	printf("%p\n", p2);
	printf("%p\n", p2 + 1);
}

对于不同的指针类型指向相同的地址,对指针进行+1运算的结果是否相同呢,通过运行结果我们可以发现运行结果是不相同的。

详解C语言指针(1)_第6张图片

对于char类型的指针进行+1运算p1存放的地址只是向后移动了一个字节,而对于int类型的指针进行+1运算p2存放的地址向后移动了四个字节。综上我们可以得出结论。指针的类型决定了指针向前或者向后走一步有多大(距离)。

三、野指针问题

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

1.野指针的成因
a.指针未初始化

当一个指针未进行初始化的时候,而使用未初始化的指针则会报错,如下面代码。

详解C语言指针(1)_第7张图片

在编译中会直接报错!

b.数组越界访问

当我们通过指针对数组进行遍历时,可能会造成指针超出了数组所开辟的空间,从而导致数组越界访问了,如下面的代码。

int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    for (i = 0; i <= 11; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }
    return 0;
}
c.指针指向的空间被释放了

下面代码就是指针指向的空间被释放造成的野指针。

int* test()
{
    int a = 10;
    return &a;
}
int main()
{
    int *p = test();
    printf("%d\n",*p);
}

在上述代码中p就是野指针,test函数中创建了一个局部变量a,a像内存中申请了4个字节,将a的地址返回给了p,但是返回之后,a所申请的四个字节就会被销毁。导致p所指向的空间不在属于a了。

2.如何避免野指针

\bullet  指针初始化

如果明确指针应该指向哪里,就初始化正确的地址

如果指针不知道初始化什么值,为了安全初始化NULL

\bullet 小心指针越界

\bullet 指针指向空间释放,及时置NULL

\bullet 避免返回局部变量的地址

\bullet  指针使用之前检查有效性

四、指针的运算

1.指针的加减常数运算

我们可以通过指针来打印出数组中的内容,代码如下

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }
    return 0;
}

arr就数组首元素的地址,我们将数组首元素的地址传递给p,p + i就是向后跳过i个整形的字节1.我们可得出 p + i与arr + i是等价的。那么*(p + i) 和 * (arr + i)也是等价。

2.指针 - 指针

首先指针-指针的前提是两个指针指向同一块区域,指针类型也得是相同的。我们看下面的代码。

int main()
{
	int arr[10] = { 0 };
	printf("%d", &arr[9] - &arr[0]);
	return 0;
}

详解C语言指针(1)_第8张图片

观察其输出结果 可以得出指针减去指针的结果是中间存放的元素的个数(小地址减去大地址是-负的元素的个数)

3.指针的关系运算

指针中存放的地址信息可以进行比较,我们观察下面两个for循环

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
    *vp = 0;
}

上面的两个for循环都是将数组中的内容置为0,但是对于第二个for循环实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。

标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较

五、指针和数组

      首先要知道指针就是指针,指针变量就是一个变量,存放的是地址,数组就是数组,可以存放一组数。但是数组的数组名是数组首元素的地址,地址可以放在指针变量中。通过下图可以清晰的看见他们的关系。

详解C语言指针(1)_第9张图片

数组名绝大时候是数组首元素的地址,但是有两个例外

sizeof(数组名),数组名单独放在sizeof内部,数组名名表述整个数组的大小,单位是字节

&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址,值是一样的但是类型和意义是不一样的

通过下面的代码和结果我们可以再次证明上述理论

详解C语言指针(1)_第10张图片

arr和&arr[0]的地址是一样的都是首元素的地址,加1后都是跳过1个整形,但是&arr + 1跳过了40个字节。

上面我们已经通过指针来遍历数组了,那么我们为什么我们可以通过指针+“1”来遍历数组呢,通过下面的代码和运行结果可以为我们解答。

详解C语言指针(1)_第11张图片

我们可以以char类型的数组为例,可以得到数组在内存中的存放是连续的,所以可以通过指针来遍历数组。

下一篇文章将从字符指针,数组指针,指针数组,数组传参和指针传参四方面继续讲解c语言指针

注:此文章仅供共同学习交流,如有错误欢迎指出!

你可能感兴趣的:(c语言)