指针就是地址, 内存单元的编号
#includeint main(void) { int * p; // p是变量名,int * 表示p变量存放的是 int类型变量的地址 int i = 3; p = &i; // p = i; // error: 类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值 // p = 55; error: 原因同上 return 0; }
p 指针变量: 可以存放变量 的 地址。
i 普通变量: 只能存放 变量的 值。
*如果一个指针变量指向了某个普通变量,则 指针变量就完全等同于普通变量
如果p是个指针变量,并且p存放了普通变量i的地址则p指向了普通变量i *p 就完全等同于 i 或者说:在所有出现*p的地方都可以替换成i。 在所有出现i的地方都可以替换成*p。
p是用来存放要读取数据的地址。 *p是让编译器从指定的地址中读取出数据。 p输出一个指针的地址,通常是输出一个16进制的数。 *p表示 指针所指向的内存地址中 存放的内容。
指针就是 地址,
地址 就是 指针。
地址 就是内存单元 的编号。
指针变量 是 存放地址的变量。
指针 和 指针变量 是两个不同的概念
但是要注意,通常我们叙述时候,会把指针变量 简称为指针。
但实际含义并不一样。
表示一些复杂的数据结构
快速传递数据
使函数能返回一个以上的值
能直接访问硬件
能够方便的 处理字符串
是 理解 面向对象语言中 引用 的基础
总结:
指针是C语言的灵魂。
地址:内存单元的编号。
一个从0开始的非负整数。
范围: 4G 为例
图解
CPU 和 内存 条 之间 有 三根线。
控制线
数据线
地址线
内存条分为很多很多格子
先把内存条 数据 读入 CPU
CPU 内部处理
处理完结果写入内促条
内存条内数据 写入硬盘
控制线: 可读,可写,控制数据传输的方向
数据线:进行数据的传输
地址:确定(在内存条上)需要控制的单元地址
一根线 有两种状态 0 和 1 可以控制2个内存单元
两根线 可以控制 4个内存单元
3根线 2的3次方
…
一般是32位,就是32根地址线,能控制 2的32次方个单位,一个单元对应8个位。 2的32次方 X 8 个位
1K = 1024 B = 2 的10次方个单元
1MB = 1024 KB = 2的20次方
1G = 1024MB = 2的30次方
2的32次方 也就是 4 G
2的32次方 是
指针的分类指针使用例子
#includeint main(void) { int i = 5; int * p; int * q; p = &i; q = p; printf("%dn",*q); return 0; }
p q r 都指向同一个地址
free§ 释放指向空间的地址
q和r不能free了
经典交换例子:
#includeint main(void) { int a = 3; int b = 5; int t; t = a; a = b; b = t; printf("a = %d, b = %dn", a, b); return 0; }
a = 5, b = 3
#include星号的含义void exchange(int *, int *); int main(void) { int a = 3; int b = 5; int t; int * p = &a; int * q = &b; printf("a = %d, b = %dn", a, b); exchange(p, q); printf("a = %d, b = %dn", a, b); return 0; } void exchange(int * a, int * b){ int t = *a; *a = *b; *b = t; }
1. 乘法 2. 定义指针变量 int * p; 定义一个名字叫做p的变量, int * 表示p只能存放 如果该运算符放在 已经定义好的指针变量 的前面 如果p是一个已经定义好的指针变量则 *p表示 以p的内容为地址的变量
如何通过 被掉函数 修改 主调函数的 普通变量 的值
1. 实参 必须为该 普通变量的地址 2. 形参 必须是 指针变量 3. 在被掉函数中 通过 *形参名=... 的方式 对主调函数相关变量的值进行修改指针和数组的关系 指针和一维数组
#includeint main(void) { int a[5]; // a是数组名 5是元素的个数 元素 就是变量 a[0] --- a[4] int b[3][4]; // 3行4列 a[0][0]是第一个元素, a[i][j]是第i+1行 j+1列元素。 return 0; }
一维数组名 是一个 指针常量
它存放的是 一维数组的 第一个元素的地址。
or
数组名是一个指针常量,表示数组第一个元素的的起始地址。
①用数组下标引|用数组元素数组a中元素用下标表示为:
a[0] a[1] a[2] a[3] a[4]
②用指针引用数组元素
数组a中元素用下标表示为: int *p = a; *p, *(p+1),*(p+2),*(p+3),*(p+4)
a 就是 a[0]的地址。
数组元素a[i] 就是当作 *(a+i)去处理的
int a[5]; int *p = a; 可以 ++p 递增指针p指向下一个数组元素,然后用*p取得元素的值。 能不能用a++或者++a把指针指向下一个数组元素? 不能!!! 开头就说过,数组名是指向数组首元素的指针常量。指针a是不可以指向其他元素的,只能指向首元素的起始地址。
#includeint main(void) { int a[5]; printf("%#Xn", &a[0]); printf("%#Xn", &a); return 0; }
0xBFEFF2E0 0xBFEFF2E0
如果 一个 函数 需要对 一维数组 进行操, 那么它 需要 几个参数 ,用来处理信息。
两个参数: 数组第一个元素 首地址; 数组的长度
#includevoid f(int *, int); int main(void) { int a[5] = {1,2,3,4,5}; int b[6] = {-1,-2,-3,-4,99}; int c[100] = {1,99,22,33}; f(a,5); f(b,6); f(c,100); return 0; } void f(int * pArr, int len){ int i; for (i = 0; i < len; i++) { printf("%d ", *(pArr + i)); } printf("n"); }
1 2 3 4 5 -1 -2 -3 -4 99 0 1 99 22 33 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
通过指针修改数组内元素
#includevoid f(int *, int); int main(void) { int a[6] = {1,2,3,4,5,6}; f(a, 6); return 0; } void f(int * pArr, int len){ int i; pArr[3] = 88; for (i = 0; i < len; i++) { printf("%d ", *(pArr + i)); } printf("n"); }
1 2 3 88 5 6
int main(void)
{
int a[5] = {1,2,3,4,5};
// a = &a[2]; // error 因为a是常量,不可以改变的
printf("%#X, %#Xn", a, &a[0]); // a 和 a[0]是同一个地址
return 0;
}
指针变量的运算
指针变最不能相加不能相乘也不能相除 如果两个指针变量指向的是同一块连续空间中的不同存储单元, 则这两个指针变量才可以相减
#includeint main(void) { int i = 5; int j = 10; int * p = &i; int * q = &j; int a[5]; p = &a[1]; q = &a[4]; printf("p和q所指向的单元间隔%d个单元n", q-p); }
p和q所指向的单元间隔3个单元一个指针变量到底占几个字节
一个指针变量,无论它指向的变量占几个字节该指针变量本身只占四个字节 一个变量的地址使用该变量首字节的地址来表示
假设 p 指向char类型变量(1个字节)
假设 q 指向int类型变量(4个字节)
假设 r 指向double类型变量(8个字节)
p q r本身所占的字节数是否一样
答案:pq r 本身所占的字节数是一样的
sizeof(数据类型)
功能:返回值 就是 该数据类型所占的字节数。
sizeof(int) = 4 sizeof(char) = 1 sizeof(double) = 8
一个指针变量,无论 它指向的那个类型 占几个字节
它自己本身 都只占8个字节
#includeint main(void) { char ch = "A"; int i = 99; double x = 66.6; char * p = &ch; int * q = &i; double * r = &x; printf("%d %d %dn", sizeof(p), sizeof(q), sizeof(r)); return 0; }
8 8 8
CPU和内存间
1根线 2(因为有0和1两种状态)
2根线 2的2次方
3根线 2的3次方
…
32根线 2的32次方
2的30次方 * 2的2次方 == 4G
内存条大小4G
编号: 从零开始,到最后一个,用32根线表示
000…0000 一共32个0,用来表示第一个地址
111…11111 一共32个1,用来表示最后一个地址
2 cpuj接收到32位地址总线的数据是32位的数据,也就是最大4字节的数据,所以统一4字节
我们只需要4个字节就可以找到所有的数据。所以,在32位的计算机中,指针占4个字节。同理,在64位的计算机中,指针占8个字节。
计算机不会像人一样思考,人可以根据你地址的大小来找适当的牌子写地址,计算机无论你地址大还是小,他都会用自己最大的牌子来写地址,而32位系统加32位软件能写地址的最大的牌子就是32位,也就是4个字节。
动态内存分配传统数组的缺点:
1.数组长度必须事先制定,且只能是常整数,不能是变量 例子: int al5]: //OK int len = 5; int a[len]; //error 2.传统形式定义的数组,该数组的内存, 程序员无法手动释放,直到函数结束,自动释放。 3.数组的长度不能在函数运行的过程中动态的扩充or缩小。 数组的长度一旦定义,其长度就不能再改变了。 4.A函数定义的数组,在A函数运行的期间可以被其它函数使用。 但是A函数运行完毕以后,A函数中的数组将无法在被其他函数使用。 传统方式定义的数组,不能跨函数使用
int main(void)
{
int a[5] = {1,2,3,4,5}; // 20个字节的存储空间,程序员无法手动编程释放它, 它只能在本函数运行完毕时由系统自动释放。
return 0;
}
动态内存分配举例
第一个字节地址本身无意义,因为你连它是什么类型都不知道,这时候要强制转换成有实际意义的地址,可以让人们使用,看得懂的
#include#include int main(void){ int i = 5; // 分配了4个字节,静态分配 int * p = (int *)malloc(4);// 12行 *p = 5; /



