指针
指针就是地址, 内存单元的编号
#include <stdio.h>
int main(void)
{
int * p; // p是变量名,int * 表示p变量存放的是 int类型变量的地址
int i = 3;
p = &i;
/**
p保留了i的地址,因此 p 指向 i。
p不是 i,i 也不是p。修改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次方 是
指针的分类
指针使用例子
#include <stdio.h>
int main(void)
{
int i = 5;
int * p;
int * q;
p = &i;
q = p;
printf("%d\n",*q);
return 0;
}
p q r 都指向同一个地址
free§ 释放指向空间的地址
q和r不能free了
经典交换例子:
#include <stdio.h>
int main(void)
{
int a = 3;
int b = 5;
int t;
t = a;
a = b;
b = t;
printf("a = %d, b = %d\n", a, b);
return 0;
}
a = 5, b = 3
#include <stdio.h>
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 = %d\n", a, b);
exchange(p, q);
printf("a = %d, b = %d\n", 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. 在被掉函数中 通过 *形参名=...
的方式 对主调函数相关变量的值进行修改
指针和数组的关系
指针和一维数组
#include <stdio.h>
int 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是不可以指向其他元素的,只能指向首元素的起始地址。
#include <stdio.h>
int main(void)
{
int a[5];
printf("%#X\n", &a[0]);
printf("%#X\n", &a);
return 0;
}
0XBFEFF2E0
0XBFEFF2E0
如果 一个 函数 需要对 一维数组 进行操, 那么它 需要 几个参数 ,用来处理信息。
两个参数: 数组第一个元素 首地址; 数组的长度
#include <stdio.h>
void 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
通过指针修改数组内元素
#include <stdio.h>
void 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, %#X\n", a, &a[0]); // a 和 a[0]是同一个地址
return 0;
}
指针变量的运算
指针变最不能相加不能相乘也不能相除
如果两个指针变量指向的是同一块连续空间中的不同存储单元, 则这两个指针变量才可以相减
#include <stdio.h>
int 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个字节
#include <stdio.h>
int main(void)
{
char ch = "A";
int i = 99;
double x = 66.6;
char * p = &ch;
int * q = &i;
double * r = &x;
printf("%d %d %d\n", 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 <stdio.h>
#include <stdlib.h>
int main(void){
int i = 5; // 分配了4个字节,静态分配
int * p = (int *)malloc(4);// 12行
/*
1. 要使用malloc函数,必须添加 <stdlib.h> 这个头文件
2. malloc函数 形参: 只有一个形参,并且是int类型
3. 4表示 请求系统为 本程序分配四个字节
4. malloc函数只能返回第一个字节的地址
但是p指向的变量所占的字节数并未确定
所以需要 int* 强制类型转换,将其转化为整型变量指针的类型 后面还有4个字节
p指向了4个字节,但是p本质上只保留了第一个字节的地址。
5. 这一行代码最终分配12个字节,p变量占8个字节,p指向的内存也占4个字节。
6. p本身所占的内存是静态分配的,p所指向的内存是动态分配的。p本身的内存只能在p变量所在的函数运行终止时由系統自动释放
*/
*p = 5; //*p代表的就是一个int变量,只不过*p这个整型变量的内存分配方式和11行的i变量,分配方式相同。
free(p); // 表示把p所指向的内存给释放掉
printf("同志们好!\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
void f(int *);
int main(void){
int * p = (int *)malloc(sizeof(int)); // sizeof(int) 返回值是int所占的字节数
*p = 10;
printf("%d\n", *p); //10
f(p);
printf("%d\n", *p); //200
return 0;
}
void f(int * q){
// *p = 200; //error
*q = 300;
}
静态分配内存 和 动态内存的比较
#include <stdio.h>
#include <stdlib.h>
int main(void){
int a[5]; // 如果int占4个字节的话,则本数组一共包括20个字节,每4个字节 为一个 int变量
int len;
int *pArr; // pArr存放了第一个字节的地址,但指向了整体前四个字节
int i;
printf("请输入你要存放的元素的个数:");
scanf("%d\n", &len);
// 动态构造一维数组
pArr = (int *)malloc(4 * len); // 动态构造一个一维数组:创造了4*len个字节,pArr是指向前4个字节 , 这是类似 我们写一个 int pArr[len]
// pArr[0]
// pArr[1] 加1就是加4个字节,加4是因为定义的是int类型
// 对动态一维数组赋值
for (i = 0; i < len ; i++) {
scanf("%d", &pArr[i]);
}
// 动态输出一维数组
for (i = 0; i < len; i++) {
printf("%d\n", pArr[i]);
}
free(pArr); //释放动态分配的数组
return 0;
}
pArr【i】 = *(pArr + i);
int main(void){
int a[5]; // 如果int占4个字节的话,则本数组一共包括20个字节,每4个字节 为一个 int变量
int len;
int *pArr; // pArr存放了第一个字节的地址,但指向了整体前四个字节
int i;
printf("请输入你要存放的元素的个数:");
scanf("%d\n", &len);
// 动态构造一维数组
pArr = (int *)malloc(4 * len); // 动态构造一个一维数组:创造了4*len个字节,pArr是指向前4个字节 , 这是类似 我们写一个 int pArr[len]
// pArr[0]
// pArr[1] 加1就是加4个字节,加4是因为定义的是int类型
// 对动态一维数组赋值
for (i = 0; i < len ; i++) {
scanf("%d", &pArr[i]);
}
// 动态输出一维数组
for (i = 0; i < len; i++) {
printf("%d\n", *(pArr + i));
}
free(pArr); //释放动态分配的数组
return 0;
}
静态内存 由 系统自动分配,由系统自动释放。
静态内存 在 栈 分配。
动态内存是 由 程序员手动分配,手动释放动
态内存是 在 堆 分配的。
多级指针
#include <stdio.h>
#include <stdlib.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;
}
#include <stdio.h>
#include <stdlib.h>
void f(int ** q){
//*q就是p
**q = 11;
}
void g(){
int i = 10;
int * p = &i;
f(&p); // p是int * 类型, &p是int **类型
printf("%d\n", i);
}
int main(void){
g();
return 0;
}
11
#include <stdio.h>
#include <stdlib.h>
void f(int ** q){ // q是个指针变量,无论q是什么类型的指针变量,都只占8个字节
int i = 5;
// *q就是p q和 **q 都不等于p
// *q = i; // error 因为*q = i; 等价于 p = i; 这样写是错误的
*q = &i; // p = &i;
// **q = 11; // 这样没问题,正确
}
int main(){
int * p;
f(&p); // p是int * 类型, &p是int **类型
printf("%d\n", *p); // 本句语法没有问题, 但是逻辑有问题。
return 0;
}
5
#include <stdio.h>
#include <stdlib.h>
void f(int ** q){
*q = (int *)malloc(sizeof(int)); // sizeof(数据类型) 返回值是该数据类型所占的字节数
// 等价于 p = (int *)malloc(sizeof(int));
// q = 5; // error
// *q = 5; // p = 5
** q = 5; // *p = 5
}
int main(){
int * p;
f(&p);
printf("%d\n",*p);
free(p);
return 0;
}
/*
动态分配内存不消失就是q的地址不消失,
q的地址不消失,
地址上存储的内容也不会消失,
也就是p不消失,
也就是p的地址不消失,
p的地址不消失上面保存的内容也不会消失
*/