NULL的含义(空指针)
NULL的本质是零,但这个零不代表数字零,表示的是内存单元的编号零。
-
指针的重要性
表示一些复杂的数据结构;
- 快速地传递数据;
- 使函数返回一个以上的值(函数中的return只能返回一个值);
- 能够直接访问硬件;
- 能够方便地处理字符串;
-
指针的定义
地址
内存单元的编号;
-
指针
指针就是地址,地址就是指针,地址是内存单元的编号。
指针变量是存放地址的变量,指针和指针变量是两个不同的概念。但通常我们叙述时,会将指针变量简称为指针,实际它们的含义并不一样。
指针的本质是一个操作受限(地址间只能相减)的非负整数。指针的分类
基本类型指针(重要)
int main(void)
{
int *p; //p是变量的名字,int *表示p变量存放的是int类型变量的地址
int i = 3;
p = &i; /*
1.p变量保存了变量i的地址,因此p指向了i;
2.p变量不是i变量,i变量也不是p,修改p的值不会影响i的值,修改i的值也不会影响p的值
3.如果一个指针变量指向了某个普通变量,则
*指针变量 等价于 普通变量
==>即*p 等价于 i
*p就是取p所指向的地址的值,其数据类型为int。
*/
return 0;
}
基本类型指针常见错误
int main(void)
{
int i = 5;
int *p;
int *q;
p = &i;
//*q = p; //*q的数据类型为int,p的数据类型为int *
//*q = *p; //q为垃圾值,程序无权限读写*q的值
p = q; //q是垃圾值,q赋值给p,p也成为垃圾值,当读写*p时,程序也会报错
printf("%d\n",*q);
/* q的空间是属于本程序的,所以本程序可以读写q的内容,
但是如果q内部是垃圾值,则本程序不能读写*q的内容,
因为此时*q所代表的内存单元的控制权限并没有分配给本程序,
所以会执行printf时会报错
*/
return 0;
}
* 星号的三种含义
乘法运算符;
- 定义指针变量,如:int * p;
- 指针运算符,即*p,表示以指针变量p所指向的地址所对应的变量的值。
通过被调函数修改主调函数普通变量的值
```c
- 一维数组名是个指针常量;
- 存放的是一维数组第一个元素的地址。
下标和指针的关系
如果p是个指针变量,则p[i]永远等价于*(p + i)。确定一个一维数组需要的参数及其原因
——即如果一个函数要处理一个一维数组,需要接收该数组的哪些信息。 ```c /一个函数输出任何一个一维数组的内容/ void f(int * pArr,int len) { for(int i = 0;i < len;i++) {
} }printf("%d",pArr[i]);
/一个函数输出任何一个字符串的内容/ void f(char pArr) { while(pArr != ‘\0’) { printf(“%c”,*pArr); pArr++; } }
/**原因如下*/
- 要输出一个数组,首先需要知道它的首地址;
- 数组没有特殊的结束符,因此完整输出时,需要知道它的长度;
- 字符串具有特殊的结束符’\0’,因此只需要知道它的首地址即可。
<a name="WkmSv"></a>
#### 指针变量的运算
- 指针变量不能相加、不能相乘、也不能相除;
- **如果两个指针变量指向的是同一块连续空间中的不同存储单元(即数组),这两个指针变量相减才有意义**。
<a name="AhrJQ"></a>
#### 指针变量的大小
- **变量的地址是该变量所占内存空间的首地址**,所占空间大小由系统根据其数据类型分配;
- **一个指针变量,无论它所指向的变量占几个字节,该指针变量本身都占用同样大小的字节空间**,具体大小视硬件平台而定。
<a name="D77xN"></a>
### 指针和二维数组
<a name="YLVrC"></a>
## 指针和结构体(重要)
<a name="wN4yl"></a>
## 多级指针
```c
#include <stdio.h>
int main(void)
{
int i = 10;
int* p = &i;
int** q = &p;
int*** r = &q;
r = &p; //error,因为r是int***类型,r只能存放int**类型变量的地址
printf("i = %d\n",***r);
return 0;
}
专题
动态内存分配(重点难点)
传统数组的缺陷
数组长度必须实现指定,且只能是常整数,不能是变量。
例子:
int a[5]; //OK
int len = 5;int a[len]; //error
传统形式定义的数组,该数组的所占内存空间无法手动释放。
- 在一个函数运行期间,系统为该函数中的数组所分配的空间会一直存在,直到该函数运行完毕后,数组的空间才会被系统释放。
- 一个数组的长度一旦定义,其长度无法再更改。
- 数组的长度不能在函数运行的过程中动态的扩大或缩小。
传统方式定义的数组不能跨函数使用。
动态数组(动态分配内存)很好的解决了传统数组的4个缺陷;
- 传统数组也叫静态数组。
malloc函数
```c 头文件:#include “stdlib.h”
原型: void* malloc(size_t size);
例子:char ptr = (char )malloc(10); // 分配10个字节的内存空间,用来存放字符
- **size 为需要分配的内存空间的大小,以字节(Byte)计**;
- malloc() 在**堆区**分配一块指定大小的内存空间,用来存放数据。**这块内存空间在函数执行完成后不会被初始化,它们的值是未知的**;
- **分配成功时,返回指向该内存的首地址(每一份地址所占空间视数据类型而定),失败则返回 NULL**;
- **函数的返回值类型是 void *,void 并不是说没有返回值或者返回空指针,而是返回的指针类型未知。所以在使用 malloc() 时通常需要进行强制类型转换**;
- 由于申请内存空间时可能有,也可能没有,所以需要自行判断是否申请成功,再进行后续操作;
- 如果 size 的值为 0,那么返回值会因标准库实现的不同而不同,可能是 NULL,也可能不是,但返回的指针不应该再次被引用;
- 如果希望在分配内存的同时进行初始化,请使用 calloc() 函数;
- **由于malloc()函数在堆区创建的,没有函数调用的入栈出栈过程,所以使用完后,动态分配的空间不会自动释放,当需要释放动态分配的内存时,调用free(p);即可**。
```c
free(p); //表示把p所指向的内存给释放掉,而p本身的内存是静态的,只能在函数执行结束后由系统释放
动态数组(动态内存分配)的构造
#include <stdio.h>
int main(void)
{
int len;
int* pArr;
int i;
pArr = (int*)malloc(4 * len); //动态构造了一个一维数组pArr,长度为len,类似于int pArr[len]
for(i = 0;i < len;i++)
{
printf("%d\n",pArr[i]);
}
return 0;
}
静态内存和动态内存的比较
静态内存
void f(int* q) { int i = 5; q = &i; //将i的值赋给p }
int main(void)
{
int p;
f(&p);
printf(“%d\n”,p); //语法没有问题,但调用完f()函数后,p存储的是i的值,
//而此时i的内存已经释放,printf()函数再次调用p时,
//p的执行超出了函数该有的权限范围
return 0;
}
//Note:动态内存分配由于使用的是堆分配,不存在入栈出栈的过程, //在函数执行后,不会自动释放,所以可以跨函数使用 ```