引言
地址和int是一个数据类型么?
int i;
// 不同数据类型占绝内存大小(64架构--编译器)
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(double));//4
printf("%lu\n", sizeof(&i));//64位8,32位4
注意:不同架构下,地址的数据类型与int不一定相同,所占内存也不一定相同
int i;
int j;
j = (int)&i;
printf("0x%x\n", &i);
printf("0x%x\n", j);
![](https://i-blog.csdnimg.cn/blog_migrate/1ef2d21b0c7bf9a1048f55a7bce20fe0.png)
变量的地址
格式化输入/输出
scanf("%d",&i);//将值输入i的地址中,即给变量i赋值
printf("0x%x\n", &i);//输出变量i的地址
printf("%p\n", &i);
//输出地址的占位符,但是我的编译器输出和%x不一样
运算符&
取地址:&……必须是变量
![](https://i-blog.csdnimg.cn/blog_migrate/9bf109c517d47718d2d4f7e49025f568.png)
注意:没有地址的东西不能取地址
&(a+b);//❌
&(a++);//❌
&(++a);//❌
相邻变量的地址
int i;
int j;
printf("i地址:0x%x\n", &i);
printf("j地址:0x%x\n", &j);
结果:
i地址:0xbd0ff544
j地址:0xbd0ff564
分析:
先定义,地址小
相邻差16(十进制),即一个字节
数组与地址
int a[5];
printf("0x%x\n", &a); //数组的地址
printf("0x%x\n", &a[0]); //数组第一个单元的地址
printf("0x%x\n", &a[1]); //数组第二个单元的地址
结果:
0x677cf8d8
0x677cf8d8
0x677cf8dc
分析:
数组地址即是第一个单元的地址
同一数组相邻单元地址差4——一个字节??一个字节是8比特,8位二进制,2位四进制
第九章 指针
1.指针的定义
1.取地址
指针:用于保存地址的变量
格式:<int><*><变量名>,变量名一般用p(point)
注意以下两种都是定义了一个指针变量p和一个整型变量q
*是跟着变量,有空格没有空格都行,不是跟着*
int*p,q;
int* p,q;
总结:
整型变量 与 指针变量
定义变量 | 变量调用 | 变量作用 | |
整型变量 | int x | x | 存储数值 |
指针变量 | int *p 或 int* p | p | 存储地址 |
运算符 & 与 *
&:取变量的地址
*:单目运算符,用于定义指针变量
指针作为参数传递给函数
必须给地址,不能给变量!
#include <stdio.h>
void f(int* p);
int main()
{
int i;
printf("0x%x\n", &i);
//验证函数参数的传递
f(&i);
int* p = &i;
f(p);
return 0;
}
void f(int *p)
{
printf("0x%x\n", p);
}
2.根据地址取变量
int k = *p;
*p = k + 1;
int i = 6;
int* p = &i;
int k = *p;
printf("0x%x\n", &i); //访问到了地址0x808ff864
printf("0x%x\n", p); //访问到了地址0x808ff864 ※※※※※※
printf("0x%x\n", *p); //访问到了变量的值6 ※※※※※※
printf("0x%x\n", k); //访问到了变量的值6
定义了一个指针变量 p,int *p
p值为地址,*p为从地址进入调取的变量的值
总结:
int i = 26;//定义了一个整型变量i,值为26
int*p;
p = &i;//定义了一个指针变量p,值为变量i的地址
int k = *p;//定义了一个整型变量,值为从p地址指向变量的值,即i的值
*p = 32;//变量i的值变为32
其他:
scanf("%d",i);//❌,但有时候不能报错!!!
2.指针的使用
课堂练习1:交换两个变量的值
#include <stdio.h>
void Swap(int* p, int* q)
{
//printf("a = %d, b = %d", a, b);
//↑ 编译报错,因为a,b不进入这个变量空间
int ret = *p;
// 根据地址交叉赋值
*p = *q;
*q = ret;
}
int main()
{
int a = 5;
int b = 6;
printf("a = %d, b = %d\n", a, b);
Swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/7ae5f45588a44ea0bbd56fe27335414a.png)
课堂练习2:有多个返回值——判断数组单元的最大值和最小值
#include <stdio.h>
void MinMax(int home[], int len, int* min,int* max);
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 }; //①注意这里数组要设置范围即a[number],不然运行会一直跑到天荒地老 修正!!这里可以用int a[]或int a[ ],直接指明number运行速度会更快
int i = 0;
int j = 1;
int* p = &i; //最小值的位置 //②注意这里要先给指针变量一个地址,而不是随便就定义了。有点像int i;但此时i的数值不定
int* q = &j; //最大值的位置
MinMax(a, sizeof(a) / sizeof(a[0]), p, q); //③注意这里数组作为参数,用a,而不是a[];另外指针要传递p,而不是*p,因为*p是变量值,p是地址,要传递地址
printf("min = %d, max = %d", *p, *q);
return 0;
}
// 函数思路是不断刷新某个地址的值,直到所有数遍历完
// 偏个题,有点像遍历循环构建素数表,但比那个简单
void MinMax(int home[], int len, int* min, int* max)
{
*min = home[0];
*max = home[0];
int i;
for (i = 0; i < len; i++)
{
if (home[i] < *min) {
*min = home[i];
}
if (home[i] > *max) {//④这里不能接上面用else,因为还有个区间范围是min~max
*max = home[i];
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/33aba7fd95f7dfb0120ce21140045716.png)
// 老师程序的简洁性
// ①main*max =中定义指针变量
int min,max;
MinMax(a, sizeof(a) / sizeof(a[0]), *min, *max);
// ②Swap函数中定义两个地址最初的值
*min = *max = home[0];
课堂练习3:不仅返回值,还返回状态——判断除法是否成功
具体分工是:函数返回值返回运算状态,结果由指针完成
返回值一般不在有效范围【有效指什么】内,通常为0或-1
当任何返回值都有效【有效指什么】时,可能要分开返回【分开返回什么】
后续语言,可以通过异常机制解决
#include <stdio.h>
int Judge(int a, int b, int* point);
int main()
{
int a = 65;
int b = 67;
int result;
if (Judge(a, b, &result)) {
printf("可以作除法。\n");
printf("%d / %d = %d\n", a, b, result);
}
return 0;
}
int Judge(int a, int b, int* point)
{
int ret = 1;
if (b == 0) {
ret = 0;
}
else {
*point = a / b;
}
return ret;
}
常见错误:
定义了一个指针变量,却没有指定让它志向哪个变量
// 以下是正确程序:
int i = 0; //定义了一个整型变量,计算机自动给它分配了一个地址
int *p;
p = &i; //指针变量的值为i的地址
// 以下是错误程序:❌❌,没有指定地址
int *q;
*q = 6;
3.指针与数组
1.数组作为函数传递参数
普通变量int x:传递进值
指针变量int *p 或 int* p:传递进值(地址)
数组int a[]或 int *a: 传递进一串值,注意必须同时给出长度
为什么不能在函数体内算出数组的长度,而要同时给出?
为什么要在函数参数定义时写空的方括号,写数字也没用?
原因:int a[] ,数组是特殊的指针!
#include <stdio.h>
void MinMax(int home[], int len);
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i, j;
printf("main sizeof(a) = %d\n", sizeof(a));
printf("main sizeof(a[0]) = %d\n", sizeof(a[0]));
printf("main a = 0x%x\n", a);
MinMax(a, sizeof(a) / sizeof(a[0]));
printf("main a[2] = %d\n", a[2]);
return 0;
}
void MinMax(int home[], int len)
{
printf("MinMax sizeof(a) = %d\n", sizeof(home));
printf("main sizeof(a[0]) = %d\n", sizeof(home[0]));
printf("MinMax a =0x%x\n", home);
home[2] = 8;
printf("MinMax a[2] = 0x%d\n", home[2]);
}
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/7ad0401a216b37c60f9fe9388e383a03.png)
分析:
由于变量只能存在于变量空间,推测函数中数组作为参数传递的是地址,给出长度,挨个读取
主函数中数组a占据40个字节,可推测有10个单元
自定函数中数组a占据8个字节,可推测有2个单元??
SUM1 数组/数组单元的格式化输出
// 检查当前编译器int和地址占绝内存大小
printf("sizeof(int) = %d\n", sizeof(int));
//结果4
printf("sizeof(&i) = %d\n", sizeof(&i));
//结果8
// 输出数组和数组单元占据内存
printf("main sizeof(a) = %d\n", sizeof(a)); // %d 输出数组a占据的内存
//结果40
printf("main sizeof(a[0]) = %d\n", sizeof(a[0]));// %d 输出数组a中单元a[0]占据的内存
//结果4
// 输出数组的地址/值
printf("main a = 0x%x\n", a); // %x 输出数组a的地址
//结果0x3b78f7e8
printf("MinMax a[0] =0x%x\n", a[0]);// 输出的是数组a中单元a[0]的值
//结果0
printf("MinMax a[0] =0x%x\n", &a[0]);// %x 输出数组a中单元a[0]的地址
//结果0x3b78f7e8
printf("MinMax a[1] =0x%x\n", &a[1]);// %x 输出数组a中单元a[0]的地址
//结果0x3b78f7ec 差了4,???这不是4个字节呀老师为什么之前说4个字节
SUM2 数组/指针的定义和使用
// 函数调用
MinMax(a, sizeof(a) / sizeof(a[0]),&i);
//注意数组作为参数输入,是a,不是a[]
//注意指针作为参数输入,是地址p,不是*p(指向变量的值)
// 函数定义
void defFunc(int home[], int len, int *p);
//注意数组作为参数输入,定义写a[]
//注意指针作为参数输入,定义写*p
// 注意
// 数组定义
int a[]; // ❌错误
// 数组作参
void f(int a[num]); // ❌错误
SUM3 数组/整数作为函数原型的多种等价方式:
//数组做参数
void defFunc(int a[], int len);
void defFunc(int *a,int len);
void defFunc(int [],int len);
void defFunc(int *,int len);
//整数做参数
void defFunc(int *,int len);
void defFunc(int *,int);
//疑————惑
void defFunc(int *,int len);
//这种情况,如果直接使用比如home[],会报错,不知道home是谁
//所以我的问题是:这种情况如何调用传递的参数???
//以上问题的解答:注意是原型中使用等价,而不是函数定义!!!!
2.指针作为数组
int min= 0;
int* p;
p = &min;
printf("*p = %d\n", *p);//p[0]就是min
printf("p[0] = %d\n", p[0]);//p[0]就是min
![](https://i-blog.csdnimg.cn/blog_migrate/b04555603ac32c3bffa3b3ac8664ec5c.png)
SUM4 数组与指针
数组变量本身表达地址:a
int a[10];
int *p = a;
数组的单元表达的是变量:a[0]
地址:&a[0] = a
值: *a = a[0]
int a[10];
a = &a[0];
a[1] = 2;
[]运算符可以对数组做,也可以对指针做
//[]--数组
int a[10];
//[]--指针
int *p;
printf("%d",p[0]);//指针只有角标为0的单元
*运算符可以对指针做,也可以对数组做
//*--指针
int *p;
//*--数组
int a[10];
*a = 25;//表示数组a,哪个变量啊???????
数组变量是const 的指针,不能再被赋值
int a[10];
int *q = a;// 可以
int b[] = a;// ❌不可以
其他:
int a[];// ❌不能直接定义这样一个数组
int a[10];
int a[] = {1,5,8,7,8,9,4};
int a[][5];// ❌不能直接定义这样一个数组
int a[3][5];
//int a[][5] = {????; 挖个坑:可以不知名行数,但要指明列数是什么来着???????
4.指针与const
![](https://i-blog.csdnimg.cn/blog_migrate/09def6d344b62805590b597e649ac05f.png)
1.指针是const:p值不能改,其他都可以
表示一旦得到某个地址,不可再被更改
int *const p = &i;//p的值就是i的地址了,是此时的地址,还是时时刻刻i的地址呢??????
*p = 25; // OK:对p指向的变量改变了值,也就是i的值改了
p++;// ❌因为p的值不可再更改
2.所指是const:*p不能从p去改对应得值,其他都可以
表示不能通过指针,去需改指向的值
注意:没有说这样会导致i本身是不可修改得,除非i是const
const int *p = &i;
*p = 25;// ❌不可以通过指向去改值
i = 25;// OK:i值可以改,因为i不是const
p = &j;// OK:p值也可以改
*p = 25;// 这回呢,从p改j??????
int a = 15;
f(&a); //OK
const int b = a;
f(&b); //OK
SUM1 判断以下表述的意思
int i;
int const *p;
const int *p;
int *const p;
技巧:观察*与const的位置,const在前说明所指是const,const在后说明指针是const
3.类型转换
应用:函数指针参数保护
void f(const int* p); // 函数声明:声明指针传进函数,函数体内的代码不会改变指向变量的值
void h(const int a[]);
4.数组与const
数组:int a[]; --> int const a,其中a是const,应用就是不能两个数组直接赋值
const int a[] = {1,2,3,5,4};
// 说明每个单元都是const,必须通过初始化赋值
5.指针运算
- 指针+ -
// 数组:同一数组指针变量+1时的地址变化
int a[] = { 0,1,2,3,4,5 };
printf("0x%x\n", &a[0]);//&a[1]
// 结果0xec4ffbb8
printf("0x%x\n", &a[1]);//&a[1]
// 结果0xec4ffbbc
int* p = a;
printf("%d\n", *p);// 指针p指向a[0]
// 结果0
printf("0x%x\n", p); //p指为&a[0]
// 结果0xec4ffbb8
p++;
printf("%d\n", *(p+1));// 指针p指向a[1]
// 结果1
printf("0x%x\n", (p+1));//p指为&a[1]
// 结果0xec4ffbbc
// 数组:不同数组指针变量+1时的地址变化(值变化:+4/1,含义变化:指向下一个单元)
char ac[] = { 0,1,2,3,4,5 };
char* p = ac;
printf("0x%x\n", p); // 0x64effad4
printf("0x%x\n", p+1); // 0x64effad5
// char数组相邻单元相差1(十进制) char 1,差一个char??????
int ai[] = { 0,1,2,3,4,5 };
int* q = ai;
printf("0x%x\n", q); // 0x64effb18
printf("0x%x\n", q + 1); // 0x64effb1c
// int数组相邻单元相差4(十进制) int 4,差一个int??????
// 数组:同一数组指针变量相减的结果(值:是变量做差,含义:代表指向两个变量作差)
char ac[] = { 0,1,2,3,4,5 };
char* p = ac;
char* p1 = &ac[3];
printf("%d\n", p1-p); //输出3
int ai[] = { 0,1,2,3,4,5 };
int* q = ai;
int* q1 = &ai[4];
printf("%d\n", q1 - q); //输出4
![](https://i-blog.csdnimg.cn/blog_migrate/fbdd0f25c262defe9ddc8c00b0c8d03e.png)
SUM1 地址的加法与减法:
两个地址作减法,p2 - p1,结果是指向的变量作差????
地址+1或-1,结果是新地址p+1或p-1。两个地址值差一个基础类型所占内存(比如int4,char1)
SUM2 数组与地址:
数组a[]的地址a就是a[0]的地址,即a = &a[0]
一个数组相邻单元的地址也是相邻的,*p = a[0],那么*(p+1) = a[1],也就是说数组中单元的地址是线性递增的
值得注意的是写法相差1,p和p+1,格式化输出发现相差1个基础类型所占内存。
2.指针++ --
SUM1 *p++
含义:取p指向变量的值,并使p = p + 1,即这个动作结束后p指向下一个地址
注意1:回顾++a和a++都是让a+1,不过此时前者值为a+1,后者仍为啊
注意2:运算符*的优先级没有++高
课堂练习:遍历数组的方法
int a[] = {0,1,2,3,4,5,6,7,8,9};
int i;
// 遍历方法① 根据数组长循环
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d", a[i]);
printf("%d", *p++);// 效果相同
}
printf("\n");
// 或
int* q;
for (q = a; q <= &a[sizeof(a) / sizeof(a[0]) - 1]; q++) {
printf("%d", *q);
}
printf("\n");
// 遍历方法②根据数组设定的截止符号循环,比如-1 while *p++
int b[] = { 0,1,2,3,4,5,6,7,8,9,-1 };
int* r = b;
while (*r != -1) {
printf("%d", *r++);
}
printf("\n");
3.指针的关系比较
关系运算符4+2都可以用
4.地址0
声明:是有为0的地址的,但少年你不要去碰,意思就是别指定一个指针为0
但,发现有些程序初始化为0了,实际上不是0,计算机自作主张了
因此,可以用0地址做特殊的事:①返回指针无效【不懂】;②指针没有被真正初始化(先初始化为0)
注意,可以用NULL来做②【可以这么理解么】
5.指针的类型
无论指向什么类型,所有指针的值即大小都是一样的,因为都是地址
但是指向不同类型的指针是不能直接互相赋值的,不同类型是有壁的!
计算机被这样设定的初衷是为了避免用错指针
6.指针的类型转换
万金油指针 void*
表示不知道指向什么东西的指针
void* 计算时可与char*相同(但不相通)【意思是不能直接p = q,p是void,q是char】
适用于底层,访问控制设备、访问外部寄存器等
指针也可以转换类型
但实际上指向变量的类型并没有变,只是人们看待它的眼光变了(这不是掩耳盗铃【能举个栗子么】
// 指针类型的强制转换
int* p = &i;
void* q = (void*)p;
// 数据类型的强制转换
char a = 8;
int b = (int)a;
等一下:现在输出地址和输出值有些混,特指很多位那种,不是%d【回顾一下,char和int占据空间比较那块的知识点】
![](https://i-blog.csdnimg.cn/blog_migrate/b6d0b5001f3521b23f90a79eb81c5c9b.png)
SUM1 指针可以用来做什么
需要传入较大的数据时做参数
传入数组,对数组才做
函数返回值不止一个结果
需要用函数修改不只一个变量
动态内存的申请
6.动态内存分配
1.malloc函数——申请一块内存空间
c99用数组,c99前用malloc函数
![](https://i-blog.csdnimg.cn/blog_migrate/80c47ff1ae52a8070e2988bece5d2a3b.png)
从man malloc可知:
#include <stdlib.h>;//必须的头文件
void* malloc(size_t sizeof(int)); //原型声明,不用打出来,知道就行
int *p;
p = (int*)malloc(n*sizeof(int));
注意返回结果类型时void,要转换成想要的类型(实际上不同类型内存占用,只是为了人类好理解,几个基础类型的空间
注意要转换成和指针指向变量类型一致的内存计算,比如
int *p;
p = (int*)malloc(n*sizeof(int));
char q*;
q = (char*)malloc(n*sizeof(char));
2.free函数——释放借的内存,和malloc搭配使用
出来借,总要还的
只能还借的部分
int *p;
p = (int*)malloc(n*sizeof(int)); // 此时p的地址,就是 0+申请的内存
free(p);
p = NULL;//还完记得把指针指向NULL,避免误操作。【是因为现在指向真正的地址0么?????】
注意事项:
借不到空间:会返回0,或者叫做NULL
还过了,又还一遍 ❌
归还不是借的空间
free(p+1); // ❌
free(NULL);// OK
不及时free,导致内存占满
新手忘了,老手没法放——多调试,多学习,多总结
![](https://i-blog.csdnimg.cn/blog_migrate/90302dcedf502d2b112a9b9d4e782f56.png)
上面演示分配了几兆的内存,为什么定义后,循环的条件是p被分配了1兆内存,驱使这个循环进行下去的动力是什么
其他:
定义指针变量的好习惯——初始化
int* p = 0;