内存中是一个一个单元,每个单元每8位(01010101)给一个编号,这个编号就是地址。
指针即地址,即内存单元的编号。
#include <stdio.h>
int main(void) {
#p是变量的名字,int * 表示p变量存放的是int类型变量的地址
int * p;
int i = 3;
/**
将i的地址放入p变量中
p保存了i的地址,因此p指向i,p不是i,i也不是p
修改p的值不影响i的值,修改i的值也不影响p的值
*/
p = &i;
p = i; //这是错的,p只能存放int类型数据的地址,不能存放int类型的值
p = 55; //error ,原因同上
return 0;
}
// *p就是以p的内容为地址的变量
void pointer(void) {
int a = 3;
int * p = &a;
int b = *p; //*p = a
printf("a = %d, *p = %d, b=%d",a, *p, b);
}
如果一个指针变量指向了某个普通变量,则*指针变量 就完全等同于 普通变量
例子:
如果p是个指针变量,并且p存放了普通变量 i 的地址,则p指向了普通变量 i
*p 完全等同于 i
或者说:
在所有出现*p的地方都可以用 i 替换
在所有出现i的地方都可以用*p替换
int *p;
- p是指针变量名,int * 表示p变量存放的是int类型的地址
- 不表示定义一个名字叫 *p 的变量
- p是变量名,p变量的数据类型是 int * 类型
所谓int * 类型就是存放int类型数据地址的类型
指针的概念
指针就是地址,地址就是指针,地址就是内存编号
地址就是内存单元(8位一字节即一个单元)的编号
指针变量:存放指针地址的变量
指针 和 指针变量 是2个不同的概念。
但是要注意:我们通常叙述时通常把指针变量简述成指针,实际上他们不一样。
指针的本质:是一个操作受限的非负整数,不能相加,不能相乘,不能相除,能相减,相减的值即:两个地址之间的间隔。
指针的重要性
- 表示一些复杂的数据结构
- 快速地传递数据
- 使函数返回一个以上的值
- 能直接访问硬件(指针即内存地址)
- 能否方便地处理字符串
- 理解面向对象语言中引用的基础
总结:指针是C语言的灵魂
指针的定义
- 什么是地址?
- 内存单元的编号
- 从0开始的非负整数
-
范围:4G内存()
控制总线
1根控制总线根据高低电频,有2个状态 :0 1,能控制2个内存单元:2字节:2 * 8 = 16 位
32位OS:32根控制总线:即 232 个状态。能控制 232 个内存单元。 232 * 8 位。
1KB = 210B(字节)
1MB = 210KB = 220B
1G = 210MB = 230B
232 = 230B x 22 = 1G * 4 = 4G
所以32位系统最大支持4G内存
指针的分类
1. 基本类型指针
下面的程序修改了一个未知地址的值=5,可能会引起其他程序崩溃。
#include <stdio.h>
int main(int) {
# p未初始化,是一个垃圾值,垃圾值的值不确定,对应不确定的的内存地址
int * p;
int i = 5;
#将5赋值给这个不确定的内存地址
*p = i;
printf("%d",*p);
}
void usualError(void) {
int i = 5;
int * p;
int * q;
p = &i;
#下面这句错误啦,*q是整数类型,p是int * 类型,类型不匹配
*q = p;
# 错误,*p 等于其存储地址中的值:5
# *q 未初始化,按其垃圾值(未知)作为地址,并将此地址中的值变为5
*q = *p;
# q是垃圾值,p的值也变成了垃圾值
p = q;
printf("%d\n", *q);
}
---------------------------------------------------------------
q是属于本程序的,所以本程序可以读取写q的内容,
但是如果q的内部是垃圾值,则本程序不能读取*q的内容。
因为*q所代表的内存单元的权限并没有分配给本程序
所以连读取*q都不行:print("%d",*q),野指针
---------------------------------------------------------------
多指针指向统一地址空间,用完需要释放
#include <stdlib.h>
void freePoint(void) {
int i = 5;
int * a = &i;
int * b = &i;
int * c = &i;
......
free(a); //只释放一次就行,再释放下面两行就有问题
// free(b);
// free(c);
}
程序报错了
free()只是将malloc()函数申请的空间释放掉,并不能将指针置空,指针的指向还是之前的,并不会改变,所以用指针与NULL比较作为循环的结束条件在双链表之中是不适合的,会导致死循环的产生。
free()只能对malloc申请的空间进行一次释放,第二次释放会出现错误
内存泄漏:
内存只使用不释放,可用内存越用越少,当为0的时候调用虚拟内存,虚拟内存也用光了就崩溃了。
附注:星号的3种含义
(1)乘法
(2)定义指针变量
int * p;
定义了一个名字为p的变量,int * 表示只能存储int类型变量的地址。
(3)指针运算符
该运算符放在已经定义好的指针变量前面
如果p是一个已经定义好的指针变量,则*p表示以p内容为地址的变量
//以下三种写法均可
int * p;
int *p;
int* p;
char A = 'A';
p = &A; //类型不匹配,错误
实参和形参
永远是不同的变量
# include <stdio.h>
# include <stdlib.h>
void test(int a, int b) { //a,b是局部变量,只在test()中使用
int t;
t = a;
a = b;
b = t;
}
void swap(int * a, int * b) {
int t;
t = *a;
*a = *b;
*b = t;
}
int main(void) {
int a = 3, b = 5; //a,b是局部变量,只在main()中使用
test(a,b); //这里只是改了形参的值,
printf("a = %d,b = %d\n",a,b); //a = 3,b = 5
swap(&a,&b);
printf("a = %d,b = %d\n",a,b); //a = 5,b = 3
return 0;
}
总结:如何通过被调函数修改主调函数的值?
- 实参必须为改普通变量的地址
- 形参必须为指针变量
- 在被调函数中通过: *形参名 =
的方式就可以修改主调函数相关变量的值。
指针使函数返回一个以上的值
2. 指针和数组
- 指针和一维数组
- 一维数组名是个指针常量
- 它存放着一维数组第一个元素的地址
# include <stdio.h>
int main(void) {
int a[5];
int b[5];
//a = b; //这是错的,a是常量,代表a[0]的地址
printf("%#X\n", &a[0]); //0X19FF2C
printf("%#X\n",a); //0X19FF2C 输出与上面相同
}
- 下标和指针的关系
# include <stdio.h>
void changeArrElement(int * arr, int len) {
//arr:形参,c:实参
//arr[2] == *(&arr[0]+2) == *(arr + 2) == *(c+2) = c[2]
arr[2] = 10;
for(int i = 0; i<len; i++)
printf("c[%d] = %d\n",i,arr[i]);
}
int main(void) {
int c[5] = {1,2,3,4,5};
printf("c[2]=%d\n" , c[2]); //c[2]=3
changeArrElement(c , 5);
printf("c[2]=%d\n" , c[2]); //c[2]=10
return 0;
}
- 指针变量的运算
指针变量不能相加,不能相乘、不能相除,只能相减。
如果2个指针变量指向的是同一块儿连续空间中的不同存储单元,则可以相减
int a = 1;
int b = 2;
int * i = &a;
int * j = &b;
//p - q没有任何意义,因为空间不保证连续
int a[5] ;
i = &a[1];
j = &a[4];
printf("i 和 j 所指向的单元相隔 %d 个单元 \n", (j - i));
- 一个指针变量到底占几个字节
- 假设p 指向char类型变量(1个字节)
- 假设q 指向int类型变量(4个字节)
- 假设r 指向double类型变量(8个字节)
p q r 本身所占的字节是否相同?
sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:sizeof(int) = 4 sizeof(long)
sizeof(变量名)
功能:返回值是该变量所占的字节数
void sizeofAddress() {
int i = 10;
double j = 20.0;
char k = 'K';
int * ii = &i;
double * jj = &j;
char * kk = &k;
// 结果是 4, 4, 4,表示都是4个字节
printf("%d, %d, %d \n", sizeof(ii), sizeof(jj), sizeof(kk));
}
一个字节一个编号,即8个01由一个编号表示,而不是1位一个编号对外表示。一个字节里面有8个位,取哪个位的编号作为这个字节的编号呢?首位!
总结
一个指针变量,无论它指向的变量占几个字节,该变量本身只占4个字节。一个变量的地址是用该变量首字节的地址表示
CPU控制内存有32根地址总线,一根线有2个状态:0 和1,32根线就有:
232个状态,记录232个字节的内存地址。
第一个地址用二进制表示就是232个0,全是0
最後一个地址用二进制表示就是232个1,全是1
专题:动态内存分配
(1)传统数组的缺点
1.数组的长度不能通过变量的形式指定
int a[5]; //OK
int lent = 5; int a[len]; /error
- 传统形式定义的数组,该数组内程序员无法手动释放
在一个函数运行期间,系统为该函数中的数组分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放 - 数组的长度一旦定义,其长度不能更改。
数组的长度不能再函数运行过程中动态地扩充或缩小 - 传统方式定义的数组不能跨函数使用
A函数定义的数组,在A函数运行期间可以被其他函数调用,但A函数运行完毕之后,A函数的中的数组将无法被其他函数调用
(2)为什么需要动态内存分配
动态数组很好地解决了传统数组的4个缺陷,传统数组也叫静态数组。
malloc : memory(内存) allocate (分配)
- 添加malloc.h头文件
- malloc(int size)函数只有一个形参,是整形
- malloc(4) : 请求系统为本程序分配4个字节
- malloc(size) 函数只能返回第一个字节的地址
- 前面需要加强制类型转换, 因为不知道
- malloc(100) 请求系统为程序分配100个字节,但是只能返回第一个字节地址,而仅仅知道第一个字节的地址无法知道此变量总共占几个字节,所以需要在前面加一个强制类型转换,告诉别人第一个字节的地址到底是什么类型的地址
- p 变量本身是静态分配的,p所指向的内存所malloc动态分配的
# include <stdio.h>
# include <malloc.h>
int main(void) {
int i = 5; //分配了4个字节,静态分配
//返回值类型是第一个字节的地址,其地址是int类型的地址
int * p = (int *) malloc(4);
//上面的语句分配了8个字节,p分配4个字节用以装地址,malloc函数又动态分配了4个,4 + 4 = 8
//*p代表的就是一个整形变量,只不过*p的内存分配方式和int p = 5; 的分配方式不同
*p = 5;
//free只能释放动态内存,静态内存只能由系统来释放,不能手动释放
free(p); //将p指向的内存释放掉。
}
# include <stdio.h>
# include <malloc.h>
void f(int * q) {
* q = 200;
//将q所指向的内存释放掉,这样的话下面的printf就出错了
// free(q);
}
int main(void) {
int * p = (int *)malloc(sizeof(int)); //
*p = 10;
printf("%d\n", *p);
f(p);
printf("%d\n", *p);
return 0;
}
void f(int * q) {
* q = 200;
//将q所指向的内存释放掉,这样的话下面的printf就出错了
// free(q);
int a[5]; //int类型变量占4个字节,本书组总共包含20个字节,每4个字节被当成一个int来使用
int len;
int * pArr;
int i;
//动态地构造一维数组
printf("请输入你要存放元素的个数:");
scanf("%d", &len);
//动态地构造了一个一维数组,长度是len,
pArr = (int *)malloc(4 * len);
for(i = 0; i < len; i++){
scanf("%d", &pArr[i]);
}
printf("一维数组的内容是:\n");
for(i = 0; i < len; i++){
printf("%d\n", pArr[i]);
}
realloc(pArr,sizeof(int) * 10); //扩充至4 x 10个字节;缩小的话,保留前面的数据 ,未执行成功,
free(pArr);
}
(3)动态内存分配举例,动态数组的构造
(4)静态内存和动态内存的比较
- 静态内存是分配在栈中,动态内存是分配在堆中。
- 栈是一种存储结构,而堆不是一种存储结构,是分配内存排序方式。
-
静态内存是由系统自动分配,由系统自动释放;动态内存写malloc()代码创建,free()代码手动释放
(5)跨函数使用内存的问题
- 静态内存是在栈里,压栈,函数执行完了就出栈,就没了释放了,所以函数内部的变量不能够跨函数使用
- 动态内存:不在栈中,在堆里。不涉及压栈出栈,可以跨函数使用。
3. 多级指针
# include <stdio.h>
# include <malloc.h>
void rr(int *** r) {
***r = 30;
}
void g(int ** q) {
**q = 20;
int *** r = &q;
rr(r);
}
void f(int * p) {
*p = 10;
int ** q = &p;
g(q);
}
void test() {
int * p = (int *)malloc(sizeof(int));
f(p);
printf("%d\n", *p);
}
int main(void) {
int i = 10;
int * p = &i;
int ** q = &p;
int *** r = &q;
printf("%d\n" , ***r);
printf("%d\n" , **q);
printf("%d\n" , *p);
printf("%d\n" , i);
test();
return 0;
}
4. 指针和函数
静态变量不能跨函数使用
# include <stdio.h>
# include <malloc.h>
void f(int ** q) {
int i = 10;
等价于p = &i; 而i的地址是f()的局部变量,
而且是静态变量,栈中的,非堆中的。
所以main()能保存和指向变量i的地址,
但是等f函数执行完后,i变量的地址就被释放了
所以访问的结果是错误的。
*q = &i;
}
正确版本
void f(int ** q) {
*q = (int *)malloc(sizeof(int));
**q = 20;
}
int main(void) {
int * p;
f(&p);
本语句语法没问题,但逻辑有问题:它访问了一个它没权限访问的地址
printf("%d\n",*p);
return 0;
}
5. 指针和结构体