C语言指针详解
C语言
指针
关于内存那点事
存储器:存储数据器件
- 外存
- 外存又叫外部存储器,长期存放数据,断电不丢失数据
- 常见的有外部存储器:硬盘、flash、rom、U盘、光盘、磁带
- 内存
- 内存又叫内部存储器,暂时存放数据,断电数据丢失
- 常见的内存设备:ram、DDR
- 虚拟内存:操作系统虚拟出来的内存
- 32bit 32根寻址总线
在写应用程序的时候,咱们看到的都是虚拟地址。
在运行程序的时候,操作系统会将 虚拟内存进行分区
堆
栈
主要存放局部变量(在函数内部,或复合语句内部定义的变量)
静态全局区
未初始化的静态全局区
静态变量(定义的时候,前面加static修饰)。或全局变量,没有初始化的,存放在此区
初始化的静态全局变量
全局变量、静态变量、赋过初值的、存放在此区
代码区
存放咱们的程序代码
文字常量区
存放常量的
内存以字节为单位来存储数据的,咱们可以将程序中的虚拟寻址空间,看成一个很大的维的字符数组
指针的相关概念
- 系统给虚拟内存的每个存储单元分配了一个编号,从
0x00 00 00 00 ~ 0xff ff ff ff
- 这个编号咱们称之为**地址**.
- 指针就是地址
- 指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号
- 在32位平台下,地址总线是32位的,所以地址是32位编号,所以指针的变量是32位的即4个字节。
注意:
- 无论什么类型的地址,都是存储单元的编号,在**32位平台下**都是4个字节。
- **即任何类型的指针变量都是4个字节大小**。
- **对应类型的指针变量,只能存放对应类型的变量地址**。
- 举例:整型的指针变量,只能存放整型变量地址
扩展:
字符变量
char ch = 'b';
ch占一个字节,它有一个地址编号,这个地址编号就是ch的地址。整型变量
int a = 0x12 34 56 78;
a 占4个字节,它占有4个字节的存储单元,有四个地址编号。整型变量a 占四个字节,分别对应四个地址,多字节的情况下地址是变量最小的,也就是
0x000020000
地址 存放的东西 0x00002003 0x12 0x00002002 0x34 0x00002001 0x56 0x00002000 0x78 0x00001fff ‘b’
指针的定义方法
简单的指针变量
数据类型 * 指针变量名;。
int * p;
定义了一个指针变量p在定义指针变量的时候 * 是用来修饰变量的,说明变量p是个指针变量
变量名是 p
关于指针的运算符
& 取地址 、 * 取值
int a = 0x1234abcd;
int *p; // 在定义指针的时候*表示修饰的意思,修饰p是个指针变量
p = &a; // 把 a 的地址给p赋值 &是取地址符p 保存了 a 的地址,也可以说 p 指向了 a
p 和 a 的关系分析: a 的值是0x1234abcd, 假如 a 的地址是: 0xbf e8 98 68
分析:
- 在调用的时候 * 代表取值的意思, *p 就相当于p指向的变量,即a,
- 故 num=*p 和 num=a 的效果是一样的。
- 所以说num的值为 0x1234abcd
扩展:
- 如果在一行中定义多个指针变量,每一个指针变量前面都需要加*来修饰
int *p,*q
// 定义了两个整数的指针变量p 和 qint *p,q;
// 定义了一个整型指针变量p,和整型变量q
int main() |
- 指针的大小:在32位系统下,所有类型的指针都是4个字节
- 指针变量是用来存放地址编号的,不管你是一个什么类型的,他都是存放的地址
int main() |
指针的分类
- 注意:多字节变量,占多个存储单元,每个存储单元都有编号
- C语言规定,存储单元编号最少的那个编号,是多字节变量的地址编号。
字符指针
字符型数据地址
char *p; //定义了一个字符指针变量,只能存放字符型数据的地址编号
char ch;
p=&ch;短整型指针
short int *p; //定义了一个短整型的指针变量p,只能存放短整型变量的地址
short int a;
p = &a;整型指针
int *p; //定义了一个整型的指针变量p,只能存放整型变量的地址
int a;
p = &a;长整型指针
long int *p; //定义了一个长整型的指针变量p,只能存放长整型变量的地址
long int a;
p = &a;float 型指针
float *p; // 定义了一个float 型的指针变量p,只能存放float型变量地址
float a;
p = &a;double 型指针
double *p;
double a;
p = &a;函数指针
结构体指针
指针的指针
数组指针
通用指针 void*p;
- 结论:**无论什么类型的指针变量,在32位系统下,都是4个字节**。
- **指针只能存放对应类型的变量的地址编号**。
指针和变量的关系
int a = 100; |
在程序中,引入变量的方法:
通过变量的名称
int a;
a = 100;通过指针变量来引入变量
int *p; // 在定义的时候, *不是取值的意思,而是修饰的意思,修饰p是个指针变量
p = &a; // 取a的地址给p赋值,p保存了a的地址,也可以说p指向了a
*p=100;// 在调用的时候*是取值的意思,*指针变量 等价于指针指向的变量注:指针变量在定义的时候可以初始化
int a;
int *p = &a; //用a的地址,给p赋值,因为p是指针变量**指针就是用来存放变量地址**的.
int main()
{
int *p1,*p2,temp,a,b; // 两个指针,三个变量
p1 = &a;
p2 = &b;
printf("请输入:a,b的值: \n");
scanf_s("%d %d",p1,p2); // 给 p1 和 p2 指向的变量赋值
temp = *p1; // 用p1 指向的变量(a)给temp赋值
*p1 = *p2; // 用p2指向的变量(b)给p1指向的变量(a) 赋值
*p2 = temp; // temp给p2指向的变量(b)赋值
printf("a = %d b = %d \n", a,b);
printf("*p1 = %d *p2 = %d", *p1,*p2);
return 0;
}- & + 变量名 == 指针
- *+指针名=变量.
扩展:
对应类型的指针,只能保存对应类型数据的地址,
如果想让不同类型的指针相互赋值的时候,需要强制类型转换
void *p
|
***+指针 取值,取几个字节,用指针类型决定的,指针为字符型指针取一个字节,指针为整型指针取4个字节,指针为double型指针取8个字节**
指针++ 指向下个对应类型的数据
字符指针++,指向下个字符数据,指针存放的地址编号加1
整型指针++,指向下个整型数据,指针存放的地址编号加4。
指针和数组元素之间的关系
变量存放在内存中,有地址编号,咱们定义的数组,是多个相同类型的变量集合
每个变量都占内存空间,都有地址编号
指针变量当然可以存放数组元素地址。
int a[5];
int *p = &a[0];
// 指针变量p 保存了数组中第0个元素的地址, 即a[0]的地址
数组元素的**引入方法**.
数组名[下标]
int a[5];
a[2] = 100;
指针名加下标
int a[5];
int *p;
p = a;
p[2]=100; // 相等于 a[2] = 100;补充:C语言规定:数组的名字就是数组的首地址,即第0个元素的地址,就是a[0],是个常量
注意:p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值。
通过指针变量运算加取值的方法来引用数组元素
int a[5];
int *p;
p=a;
*(p+2)=100; //相等于a[2] = 100
// 解释: p 是第0个元素的地址,p+2是a[2]这个元素的地址.
// 对第二个元素的地址取值,即a[2]通过数组名+取值的方法引用数组的元素
int a[5];
*(a+2)=100; // 相等于a[2] = 100;
// 注意: a+2 是 a[2]的地址.这个地方并没有给a赋值.例如:
int main()
{
int a[5] = {0,1,2,3,4};
int *p;
p = a;
printf("a[2] = %d\n",a[2]);
printf("p[2] = %d\n",p[2]);
printf("*(p+2) = %d\n",*(p+2));
printf("*(a+2) = %d\n",*(a+2));
printf("p = %p\n",p);
printf("p+2 = %p\n",p+2);
return 0;
}
指针的运算
指针可以加上一个整数,往下指几个它指向的变量,结果还是个地址
前提:指针指向的数 组元素的时候,加一个整数才有意义。
int a[5];
int *p;
p = a;
p+2; // p是a[0]的地址,p+2 是&a[2]
// 假如p保存的地址是 2000的话, p+2 代表的地址编号是 2008char buf[5];
char *q;
q=buf;
q+2; // 相等于&buf[2]
// 假如: q中存放的地址编号是 2000 的话,q+2 代表的地址编号是 2002两个相同类型的指针可以比较大小.
前提:只有两个**相同类型的指针指向同一个数组元素**的时候,比较大小才会有意义
**指向前面元素的指针 小于 指向后面元素的指针**。
int a[10];
int *p,*q;
p = &a[1];
q = &a[6];
if(q > p){
printf("q > p");
}两个相同类型的指针可以做减法
前提:只有两个**相同类型的指针指向同一个数组元素**的时候,做减法会有意义
做减法的结果是,两个指针指向的中间有多少个元素
int main()
{
int a[10];
int *p,*q;
p = &a[1];
q = &a[6];
printf("%d\n",q-p); // 中间相差了几个元素
return 0;
}两个相同类型的指针可以相互赋值
注意:只有相同类型的指针才可以相互赋值(void*类型的除外)
int *p,*q;
int a;
p = &a; // 保存了a的地址
q = p; //用p给q赋值,q也保存了a的地址,指向a注意:C语言规定数组的名字,就是数字的首地址,就是数组的第0个元素的地址
int *p;
int a[10];
p = a; p = &a[0]; 这两种方法是等价的
指针数组
目的就是把多个变量的指针存到一起,从而出现指针数组
指针和数组关系.
- 指针可以保存数组元素的地址
- 可以定义一个数组,数组中有**诺干个相同类型变量**,这个数组被称为指针数组
**指针数组的概念**:
指针数组本身是个数组,是个指针数组,是**诺干个想用类型的指针变量构成的集合**。
指针数组的定义方法
类型说明符 * 数组名[元素个数];
int *p[5];//定义了一个整型的指针元素数组p,有5个元素p[0]~p[5],每个元素都是int* 类型的变量
int a;
p[0]=&a;
int b[10];
p[1] = &b[5];
// p[2] *(p+2)是等价的,都是指针数组中的第2个元素打印字符数组
int main()
{
char *name[5] = {"Hello","China","everyday","pink","green"};
int i;
for(i = 0; i < 5; i++){
printf("%s\n",name[i]);
}
return 0;
}
指针的指针
指针的指针,其实就是指针的地址
- 咱们定义一个指针变量本身指针变量占4个字节,指针变量也有编号。
- 例:
int a = 0x12345678;
- 假如:a的地址是 0x00002000
- int *p;
- p = &a;
- 则p中存放的是a的地址编号即 0x00002000
- 因为**p也占四个字节,也有自己的地址编号,即指针变量的地址,即指针的指针**(就是套娃
- 我们定义一个变量存放p的地址编号,这个变量就是指针的指针
- int **q;
- q=&p; // 保存了p的地址,也可以说q指向了p
- 则q里存放的就是0x00003000
|
字符串和指针
字符串的概念:
- 字符串就是以**‘\0’结尾的诺干个字符的集合**,比如:”helloworld”。
- 字符串的地址,是**第一个字符的地址。如:字符串”hello” 的地址,其实就是字符串’h’的地址**。
- 我们可以定义一个字符指针变量保存字符串的地址,比如:
char *s="hello";
字符串的存储形式:数组、文字常量区、堆.
字符串其实存放在数组中
其实就是在内存(栈、静态全局区)中开辟了一段空间来存放字符串。
char string[100]="I love C!";
定义了一个字符串string,用来存储多个字符,并且用”I love C!”给string数组初始化,字符串”I love C!” 存放在string 中。
字符串存放在文字常量区
在文字常量区开辟了一段空间存放字符串,将字符串的**首地址**赋给指针变量。
char *str="I love C!";
定义了一个指针变量str,只能存放字符地址编号
I love C! 这个字符串中的字符不是存放在str指针变量中
str只是存放了字符 I 的地址编号,”I love C!” 存放在文字常量区
字符串可以存放在堆区
使用malloc等函数在堆区申请空间,将字符串拷贝到堆区
char *str=(char*)malloc(10*sizeof(char));
//动态申请了10个字节的存储空间,首地址给str赋值。
注意:
- 普通全局数组,内存分配在静态全局区
- 普通局部数组,内存分配在栈区。
- 静态数组(静态全局数组、静态局部数组),内存分配在静态全局区
字符串的可修改性:
字符串内容是否可以修改,取决于字符串存放在那里
存放在数组中的字符串的内容是可以修改的
chat str[100]="I love C!";
str[0]='y';
// 正确可以修改的注: 数组没有同const 修饰
int main()
{
char str[50] = "I love C!";
printf("str=%s\n",str);
str[0]='y';
printf("str=%s\n",str);
return 0;
}**文字常量区里的内容是不可修改**的
chat *str="I love C!";
*str='y';
// 错误,I存放在文字常量区,不可修改注:
- str指向文字常量区的时候,它指向的**内容不可被修改**.
- str是指针变量可以指向别的地方,即**可以给str重新赋值**,让它指向别的地方
堆区的内容是可以修改的
int main()
{
char *str;
str = (char*)malloc(20);
strcpy(str,"I love C!");
printf("str=%s\n",str);
*str = 'y'; // 可以被修改,因为堆区内容是可修改的
printf("str=%s\n",str);
return 0;
}str**指向的堆区的时候,str指向的内存内容是可以被修改**的
**str是指针变量,也可以指向别的地方**。即可以给str重新赋值,让它指向别的地方
注意:str指针指向的内存能不能被修改,要看str指向哪里。
- str指向文字常量区的时候,内存里的内容不可修改
- str指向数组(非const修饰)、堆区的时候,他指向内存的内容是可以修改的
初始化
字符数组初始化
char buf_aver[20]="hello world";
指针指向文字常量区,初始化
char *buf_point="hello world";
指针指向堆区,堆区存放字符串
不能初始化,只能先给指针赋值,让指针指向堆区,再使用strcpy、scanf 等方法吧字符串拷贝到堆区。
char *buf_heap;
buf_heap=(char*)malloc(15);
strcpy(buf_heap,"hello world"); // 两种方式,一般使用第一种,第二种可能会警告
scanf("%s",buf_heap);
使用时赋值
字符数组:使用scanf 或者 strcpy
char buf[20]="hello world";
buf="hello kitty"; // 错误,因为字符数组的名字是个常量,不能用等号给常量赋值
strcpy(buf,"hello kitty"); // 正确,数组中的内容可以修改的
scanf("%s",buf); // 正确,数组中的内容是可以修改的指针指向文字常量区
char *buf = "hello world";
buf="hello kitty"; // 正确,buf指向另一个字符串
stcpy(buf,"Hello kitty"); // 错误,这种情况,buf指向的是文字常量区,内容只读。
// 当指针指向的文字常量区的时候,不能通过指针修改文字常量区的内容指针指向堆区,堆区存放字符串
char *buf_heap;
buf_heap=(char*)malloc(15);
strcpy(buf_heap,"hello world"); // 两种方式,都可以
scanf("%s",buf_heap);
字符串和指针的总结
指针可以指向文字常量区
- 指针指向的文字常量区的内容不可改变
- 指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向别的地方。
指针可以指向堆
- 指针指向的堆区的内容可以修改。
- 指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向被动地方
指针也可以指向数组(非const修饰)
char buf[20]="hello world";
char *str=buf;这种情况下
- 可以修改buf 数组的内容
- 可以通过 str 修改 str 指向的内存的内容,即数组buf的内容
- 不能通过buf赋值
buf="hello Kitty";
错误的 - 可以给str赋值,及str指向别处. str=”Hello Kitty”;