C语言第六章:指针
一,什么是指针
1.要想了解指针的定义,必须首先了解计算机地址的定义。
- 计算机地址是指内存条的内存单元的编号。是从0开始的非负整数。
- 地址的范围是由内存条的大小决定的。
- 4G内存的地址是在:0-4G-1范围内
2.其次要了解计算机的CPU和内存条之间的关系
- CPU要对内存条进行控制的话需要三根线:控制总线,数据总线,地址总线
- 控制总线:控制总线是控制CPU是对内存条是进行只读操作,还是只写操作,还是可读可写操作的。
- 数据总线:数据总线是CPU和内存条进行数据交换的通道。
- 地址总线:地址总线是CPU进行寻址的,即CPU要对那个内存地址进行操作,例如地址总线有32根,即可以对2的32次方个内存地址进行操作,也就是4G,此时如果是4G内存条,就可以完全做到寻址
3.指针的含义
- 指针就是地址,地址就是指针。
- 指针变量就是存放内存单元编号的变量,或者说指针就是存放地址的变量。
- 指针和指针变量是两个完全不同的概念。
- 通常我们叙述时,会把指针变量简称为指针,实际它们的含义并不一样,指针本质其实是一个操作受限的非负整数
二,指针的定义
1.指针变量的基本定义:
# include<stdio.h> int main(void) { int i = 10; int * p;// p是变量名,int *是数据类型,表示p变量只能存放int类型变量的地址 p = &i; // 把变量i的地址赋值给p, return 0; }
2.指针变量的解释:
- 指针变量p保存了变量i的地址,因此可以说指针p指向变量i。
- p不是i,i也不是p。修改p的值不会影响i的值,修改i的值不会影响p的值。
- 如果一个指针变量p指向一个普通变量i,则*p == i。*p的准确解释就是:*p表示的是以p的内容为地址变量。
3.经典的指针程序:
互换两个变量的值
# include<stdio.h> void fun(int * p,int * q){ int temp; temp = *p; *p = *q; *q = temp; } int main(void) { int i = 10; int j = 20; fun(&i,&j); printf("%d %d",i,j); return 0; }
三,一维数组与指针的关系
1.一维数组的名称:
- 一维数组的名称其实就是一个常量,它表示一维数组的第一个元素的地址。
# include<stdio.h> int main(void) { int arr[5] = {1,2,3,4,5}; printf("%#X %#X \n",arr,&arr[0]); /* 打印的结果相同:因此说明arr其实就是数组第一个元素的地址 */ return 0; }
2.一维数组下标和指针的关系:
- 如果指针变量p指向数组arr,则p[i] = arr[i]。
- 数组下标与指针的关系:arr[i] = *(p+i);
- 当p+1的时候,实际上是指针p移动到下一个地址,这个地址不是简单的加1运算,而是根据数据类型的长度而计算出来的,例如int数组,则p+1实际上是指针p移动了4个字节
# include<stdio.h> int main(void) { int arr[5] = {1,2,3,4,5}; int * p; p = arr; // p指向arr,因此p[i] == arr[i] printf("%d \n",p[3]); int temp0 = *p; // *p 表示的是数组的第一个元素 printf("%d \n", temp0); int temp1 = *(p+1); // *(p+1) 表示的是数组的第二个元素 printf("%d \n", temp1); // 根据上面的结果我们推算出:arr[i] == *(p+i); return 0; }
3.一个函数处理一个数组用指针需要接收几个参数:
- 这个函数的形参需要两个参数:数组的名称和数组的长度。
- 通过上面两个参数,就可以让指针完全代替数组进行运算。
4.指针变量的运算:
- 指针变量不能相加,不能相减,不能相乘,不能相除。
- 如果指针变量指向的是同一块连续空间的不同内存单元,则可以进行加法和减法。
5.一个指针变量占几个字节:
- sizeof(数据类型/变量名)函数,返回该数据类型/变量名所占的字节数。
- 指针变量无论它指向的数据类型占几个字节,其所占字节数都为4个字节。
# include<stdio.h> int main(void) { int a = 0; char b = 'A'; double c = 23.12; printf("%d %d %d \n",sizeof(a),sizeof(b),sizeof(c)); // 输出结果为:4 1 8 int * x = &a; char * y = &b; double * z = &c; printf("%d %d %d \n",sizeof(x),sizeof(y),sizeof(z)); // 输出结果为:4 4 4 return 0; }
四,动态内存与静态内存
1.什么是多级指针:
- 存放指针的指针就是多级指针
# include<stdio.h> int main(void) { int i = 10; int * p = &i; // 将变量i的地址赋值给指针p int **q = &p; // 将指针变量p的地址赋值给指针q(因为p的数据类型为int *,所以指针p的地址是int **) return 0; }
2.多级指针的理解:
- 多级指针需要理解的是指针的定义,即指针是用来存放某种数据类型的地址,所以这个某种类型也可以是指针变量,所以会出现多个*叠加的情况,这个是需要理解的。
3.传统数组的缺点:
- 数组的长度必须事先指定,切不能为变量,必须为常整数。
- 传统定义的数组,程序员无法进行内存的释放,只能等函数执行完成后,函数弹出栈后,内存才会释放。
- 数组长度在函数运行过程中,不能动态的增加和缩减。
- 数组的存在时间只能在函数的运行期间存在,函数弹栈释放内存后,无法再被使用。
4.为什么需要动态内存:
- 动态内存很好的解决了传统数组的4个缺陷问题。
- 传统数组也叫做静态数组。
- 所有函数内部定义的操作都是静态内存,由系统自己分配内存空间,由系统自己释放内存空间。
5.什么是动态内存:
- 动态内存是允许我们自己向系统申请一块内存空间,该内存空间的创建和释放是掌握在程序员的手里
- 动态内存是在堆内存中分配的
- 静态内存是在栈内存中分配的
6. malloc 函数
- malloc是memory allocate的缩写,中文含义是内存分配。
- malloc(n)函数用于向系统中申请分配n个字节的连续内存空间。返回类型是void *类型。
- void *类型表示未确定类型的指针,因为我们在申请内存空间的时候,并不知道这块内存空间用来存放什么样的数据类型。因此C/C++规定,void *类型可以通过数据类型的强制转换而成为其他类型的指针(例如:int * , char *)。
- malloc函数使用之前,必须引入头文件:# include <malloc.h>
7. 动态内存分配示例
假设我们构造一个长度为len的int类型的一维数组。
int * p = (int *)malloc(len);
1. 这条语句分配了两块内存,一块是长度为len的动态内存,一块为静态内存。并且这块静态内存是p变量本身所占的内存,占4个字节(因为是一个指针变量)。
2. malloc函数的功能是向系统申请len个字节的内存空间,如果请求分配成功,则返回第一个内存单元的地址,分配不成功则为null
3. malloc函数只能返回第一个字节的地址,所以我们需要把这个没有实际含义的地址(俗称干地址)转换成一个有实际意义的地址,因此malloc函数前面必须加(数据类型 *),表示把这个无实际意义的地址转 换成相应数据类型的地址,这样我们的内存空间就会按照该数据类型进行划分(例如:int *则会将这个len为100字节的内存单元划分成实际存储25个int类型变量)。
4. 例如:int * p = (int *)malloc(100);
表示将系统分配的100个字节的内存空间的第一个地址转换成int类型的地址,由于int类型是占4个字节,所以此时的第一个字节的地址转化成前四个字节的地址。所以p指向的是第一个4个字节的地址,p+1则 是第二个4个字节的地址,依次类推。。。申请的这个内存空间,可以存放25个int类型的变量。
8. free 函数
- 该函数是用于释放动态内存的函数,例如上面的分配的100个字节的动态内存,我们程序员如果想要释放的话,就可以调用此函数:free(p);就会将该内存释放掉。
- free(p)的含义是指将p指针所指向的内存空间进行释放,p变量本身的内存是静态内存,是随着函数执行的结束而释放的。
9. 动态内存和静态内存的比较
- 静态内存是由系统进行分配,由系统进行释放。静态内存是在栈内存中进行分配的。
- 动态内存是由程序员手动分配,由程序员手动释放。动态内存是在堆内存中进行分配的。
- 静态内存不能够跨函数使用,因为静态内存是在栈内存中分配的,在函数执行完毕之后,函数出栈,系统释放掉函数所占的内存,所以无法被其他函数继续使用。只能够在函数运行期间进行使用。
- 动态内存是可以跨函数使用的,因为动态内存是在堆内存中进行分配的,动态内存的生命周期由程序员进行控制,因此其他内存在函数执行完毕之后是依然可以继续使用的。