day09
八、指针
指针的复杂类型总结:
搬运:http://t.csdnimg.cn/yeq1e
int p; //这是一个普通的整型变量
int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
1.概念
1)指针就是地址,地址就是指针
2)什么是地址?
在内存中,有多个内存单元构成,每个内存单元占8位(bit)–>字节,内存中以字节为单位,没一个字节都有相应的编号,这个编号就叫做地址。地址具有唯一性
3)32位机
代表寻址能力
最小地址编号:0000 0000 0000 0000 0000 0000 0000 0000
--> 0x00000000
最大地址编号:1111 1111 1111 1111 1111 1111 1111 1111
--> 0x11111111
4)指针:指针变量、地址、指针的统称
用来存储不同的地址常量的叫做指针变量
2.指针变量
1)指针变量的定义
<存储类型><数据类型> * <变量名>;
//*用在定义变量时,表示该变量是一个指针变量
int *p;
//表示定义了一个整型的指针变量p
eg:
int a = 10,b = 20;
int *p;
p = &a;
//当前指针变量p存储的是a的地址值,指针p指向a
//指向:指针保存哪个变量的地址,就可以说指针指向哪个变量
2)指针变量的初始化
eg:
int a = 10,b = 20;
int *p = &a;
char *q = NULL;//指针q指向零地址,避免成为野指针
3)直接访问与间接访问
直接访问:直接通过变量的地址去访问
间接访问:通过存放变量地址的指针变量去访问变量
4)指针的运算
关系运算、算术运算、赋值运算
&:
取地址,取变量的地址
*:
1.代表乘运算符
2.用在定义变量时,代表定义指针变量
3.取内容
取该指针所指向地址空间中的内容
&和*互为逆运算
5)指针的偏移
p + 1:以p所指向的地址为基准,想高地址偏移1个数据类型所占的字节
int a = 10;int *p = &a;p + 1//往高地址偏移4个字节
p - 1:以p所指向的地址为基准,想低地址偏移1个数据类型所占的字节
int a = 10;int *p = &a;p - 1//往低地址偏移4个字节
P + n:以p所指向的地址为基准,想高地址偏移n个数据类型所占的字节
P - n:以p所指向的地址为基准,想低地址偏移n个数据类型所占的字节
p - q:指针p与指针q之间相隔的元素的个数,p与q必须为同类型
单独使用:
++p/p++:以p所指向的地址为基准,向高地址偏移一个数据类型(指针所指向的数据类型的大小),p的指向改变
--p/p--:以p所指向的地址为基准,向低地址偏移一个数据类型(指针所指向的数据类型的大小),p的指向改变
结合使用:
*p++: p先与++结合,此时p的指向要改变
(*p)++: p先与*结合,此时p的指向不变
*++p: p先与++结合,此时p的指向改变
*(++p): 同上
eg:
int a[5] = {10,20,30,40,50};
int *p = a;
printf("a = %d\n",*p);//a = 10
printf("a = %d\n",*(p)++);//a = 10
printf("a = %d\n",*p);//a = 20
printf("a = %d\n",*p);//a = 10
printf("a = %d\n",(*p)++);//a = 10
printf("a = %d\n",*p);//a = 11
printf("a = %d\n",*p);//a = 10
printf("a = %d\n",*++p);//a = 20
printf("a = %d\n",*p);//a = 20
printf("a = %d\n",*p);//a = 10
printf("a = %d\n",*(++p));//a = 20
printf("a = %d\n",*p);//a = 20
6)指针的数据类型:
指针的数据类型与指针所指向的数据类型一致
大端:高地址存放低字节,低地址存放高字节
小端:低地址存放低字节,高地址存放高字节
(0x12345678) --> 从左到右,由高到低字节
eg:
int a = 0x12345678
char *p = &a;
printf("%#x\n",*p) //打印0x78
7)指针的大小 8字节
int a = 10;
int *p = &a;
char *q = NULL;
printf("%d\n",sizeof(p));//8
printf("%d\n",sizeof(q));//8
3.指针与一维数组
int a[5] = {10,20,30,40,50};
一维数组数组名:
代表整个一维数组空间
代表一维数组的首地址 --> 数组第一个元素的地址
[]: 变址运算符(偏移并引用)
eg:
int a[5] = {10,20,30,40,50};
a[0] <==> *(a + 0);
int *p = a;
*(a + 0) <==> *(p + 0);
a[0] <==> *(a + 0) <==> *(p + 0);
eg: 使用指针打印一维数组
for(int i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
//printf("%d ", a[i]);
//printf("%d ", *(a + i));
}
4.指针与字符串数组
1)eg:
int main(int argc, char *argv[])
{
char s[] = "hello";
char *p = s;
puts("hello");
//定义一个字符指针q,指向字符串常量的首元素
char *q = "hello";
puts(q);
printf("%p\n",s);
printf("%p\n",p);
//q指向字符串常量“hello”的首地址,不能修改常量的值
*q = 'A';
//putchar(*(q+3));//输出l
/*
puts(s);输出hello
puts((p+3));//输出lo
printf("%s\n",p);//输出hello
*/
return 0;
}
2)指针指向字符串常量首地址
char *p = "hello";
putchar(*p); --> h
*p = 'A' //段错误,字符串常量存储在常量区,不能修改常量中的内容
补充:在C语言中,当你使用 char *p = "hello"; 这样的语句时,你实际上是在创建一个指向字符串常量 "hello" 的指针 p。字符串常量 "hello" 通常存储在只读内存区域(如文本段或常量段),这意味着你不能修改该内存区域中的内容。
操作系统层面的虚拟内存空间:
在操作系统层面,虚拟内存空间可以分为用户空间和内核空间两大部分。
用户空间:
- 这是提供给各个进程主要的空间,即操作系统提供给各个进程的一致并且独立的虚拟地址空间。
- 对每个进程而言,虚拟地址空间被更细致地划分为代码区、数据区、堆区、栈区、未使用区等部分。
- 代码区:存放应用程序的机器代码,运行过程中代码不能被修改,具有只读和固定大小的特点。
- 数据区:存放了应用程序中的全局数据、静态数据和一些常量字符串等,其大小也是固定的。
- 堆区:运行时程序动态申请的空间,属于程序运行时直接申请、释放的内存资源。
- 栈区:用来存放函数的传入参数、临时变量,以及返回地址等数据。
- 未使用区:分配新内存空间的预备区域。
内核空间:
- 主要指操作系统运行时用于程序调度、内存分配、连接硬件资源等程序逻辑使用的内存空间。
- 内核空间包含多个模块,如进程管理调度、内存管理调度、系统中进程间通信、虚拟文件系统、网络子系统、驱动等
eg:逆序输出字符串中的数组
int main(int argc, char *argv[])
{
char s[] = "hello";
char *p = s;
char *q = s+strlen(s)-1;
while(p < q)
{
char temp = *p;
*p = *q;
*q = temp;
p++;
q--;
}
puts(s);
return 0;
}
5.多级指针
指向指针的指针
存储指针变量的地址的指针
1)二级指针
<存储类型><数据类型>**<变量名>;
//**代表定义的是一个二级指针
*:取该指针所指向地址空间中的内容
int main(int argc, char *argv[])
{
int a = 10;
int *p = &a;
int **pp = &p;
printf("%p\n",&a);
printf("%p\n",p);
printf("%p\n",&p);
printf("%p\n",pp);
return 0;
}
打印结果:
/*0x7fff38958824
0x7fff38958824
0x7fff38958828
0x7fff38958828
*/
*pp = &a = p;//不是&p
**pp = *p = a = 10;//不是&a
6.指针与二维数组
int a[2][3] = {{1,2,3},
{4,5,6}};
int *p = &a[0][0]; --> a[0]
eg:一级指针遍历二维数组
int main(int argc, char *argv[])
{
int a[2][3] = {{1,2,3},{4,5,6}};
//int *p = &a[0][0];
int *p = a[0];
for(int i = 0; i < 6; i++){
printf("%d ", *(p+i));
if(i == 2)
puts("");
}
puts("");
/*
printf("%p\n", &a[0][0]);
printf("%p\n", &a[0][0] + 1);
printf("%p\n", a[0]);
printf("%p\n", a[0] + 1);
*/
return 0;
}
eg1:使用一级指针对二维数组所有元素求和
int main(int argc, char *argv[])
{
int a[2][3] = {{1,2,3},{4,5,6}};
int sum = 0;
for(int i = 0; i < 6; i++)
{
sum+= *p++;
}
printf("sum = %d\n",sum);
return 0;
}
eg2:使用指针对二维数组进行冒泡排序
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[2][3] = {3,2,5,7,0,-1};
int *p = &a[0][0];
for(int i = 0; i < 6; i++)
{
printf("%d ",*(p+i));//这里不能使用p++,会报段错误,因为p++,p的指向一直在改变,下面p的指向会超出范围,所以会报段错误,除非重置p的指向
if(i == 2)
puts("");
}
puts("");
for(int i = 0; i < 5; i++)
{ //冒泡排序循环轮数
for(int j = 0; j < 5-i; j++)
{//每轮循环比较次数
if(*(p+j) > *(p+j+1))
{
int temp = *(p+j);
*(p+j) = *(p+j+1);
*(p+j+1) = temp;
}
}
}
printf("排序好后的数组为:\n");
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 3; j++)
{
printf("%d ",a[i][j]);
}
puts("");
}
return 0;
}
7.数组指针
int main(int argc, char *argv[])
{
int a[] = {1,2,3};
printf("%p\n", a);
printf("%p\n", a+1); //偏移4字节
printf("%p\n", &a);
printf("%p\n", &a+1); //偏移12字节
puts("***************");
printf("%p\n", *&a); //&进行升维,*降维,&a 该地址能引用12个字节 *&a --> a a地址能引用4字节
printf("%p\n", *&a + 1); //偏移4字节
printf("%d\n", *(*&a + 1)); //先偏移4字节,再取当前地址空间中的内容
puts("***************");
int (*p)[3] = &a; //定义一个行指针变量p
printf("%p\n", *p);
printf("%p\n", *p + 1);
printf("%d\n", *(*p + 1));
return 0;
}
1)数组指针的定义
<存储类型><数据类型>(* 指针变量名)[元素个数];
eg:
元素个数: 一次能引用的元素个数
int (*p)[3];
eg:
int a[2][3] = {0};
1)二维数组的数组名代表二维数组首行的地址
eg:
int main(int argc, char *argv[])
{
int a[2][3] = {10,20,30,40,50,60};
printf("%p\n",a); //二维数组首行地址
printf("%p\n",a+1); //a+1偏移整行
puts("**************");
int (*p)[3] = a;//定义一个行指针,指向二维数组a的首行
printf("%p\n",p);
printf("%p\n",p+1); //偏移整行字节空间
puts("**************");
printf("%d\n",*(*p)); //*p 降维操作,*p代表一个地址,该地址能引用4字节空间,*(*p)取内容
printf("%d\n",*((*p+1)+2));//让行指针偏移到第二行
puts("**************");
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 3; j++)
{A
//printf("%d ", *(*(p+i)+j));
//printf("%d ", *(*(a+i)+j));
//printf("%d ", a[i][j]);
//printf("%d ", p[i][j]);
//printf("%d ", (*(p+i))[j]);
printf("%d ", *(p[i]+j));
}
puts("");
}
return 0;
}
8.指针数组
用来存储大量同类型的指针变量
1)一般形式
<存储类型><数据类型> *数组名[数组元素个数];
int *p[3];//定一个指针数组,该数组存储int*类型的指针
eg:
int a = 10, b = 20, c = 30;
char d = 'a';
int *arr[4] = {&a,&b,&c};//定义一个指针数组arr,该数组存放int *类型的指针
printf("%d\n",*arr[0]);//arr[0] arr数组第一个元素,*arr[0]1取变量a所取的值
int **p = arr;//int *类型的指针数组名是一个二级指针
eg2:
char str[][64] = {"hello", "world", "welcom", "to", "chengdu"};
char *P_str[5] = {NULL};
for(int i = 0; i < 5; i++){
P_str[i] = str[i]; //指针数组P_str各个元素进行赋值
}
eg3:
int main(int argc, char *argv[])
{
char str[][64] = {"hello", "world", "welcom", "to", "chengdu"};
//char *P_str[5] = {"hello", "world", "welcom", "to", "chengdu"};
char *P_str[5] = {NULL};
for(int i = 0; i < 5; i++){
P_str[i] = str[i];
}
char **pp = P_str; //定义一个二级指针pp指向数组P_str首元素地址
puts(pp[0]);
puts(*(pp+0));
puts(P_str[0]);
puts(*(pp+2));
printf("%c\n", *P_str[0]);
printf("%c\n", *(P_str[2]+3));
printf("%c\n", *(*(pp+2)+3));
printf("%c\n", pp[2][3]);
*P_str[0] = 'H';
*P_str[2] = 'H';
return 0;
}
2) main函数参数
argc:代表命令行参数个数
char *argv[]: 指针数组,用来保存命令行参数中的字符串
eg:
int main(int argc, char *argv[])
{
for(int i = 0; i < argc; i++){
puts(argv[i]);
}
return 0;
}
eg1: 使用指针数组对字符串进行排序,按照字符串长度进行从小到大排序
int main(int argc, char *argv[])
{
char str[][64] = {"hello", "world", "welcom", "to", "chengdu"};
char *P_str[5] = {NULL};
for(int i = 0; i < 5; i++){
P_str[i] = str[i];
puts(P_str[i]);
}
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4-i; j++){
if(strlen(P_str[j]) > strlen(P_str[j+1])){
char *temp = P_str[j];
P_str[j] = P_str[j+1];
P_str[j+1] = temp;
}
}
}
puts("*********************");
for(int i = 0; i < 5; i++){
puts(P_str[i]);
}
return 0;
}
[2] 使用二级指针完成上诉题目
9.特殊指针
1)万能指针
万能指针可以指向任意数据类型
万能指针不能直接解引用,必须先强转为某一数据类型
eg:
int a = 10;
void *p = &a;
printf("%d\n",*(int *)p);
2)const 修饰指针
eg1:
const int *p = &a;
*p = 100;//error 当前指针所指向地址空间中的内容不允许被修改
可以修改指针的指向,但不能修改指针所指向的地址空间中的内容;
eg2:
int * const p = &a;
*p = 100;//right
p = &a;//error 不能修改p的指向
可以修改指针所指向的地址空间中的内容,但不能修改指针的指向;
eg3:
const int * const p = &a;
*p = 100; //error 不能修改指向地址空间中的内容
p = &b; //error 不能修改p的指向
既不能修改指针所指向地址空间的内容,也不能修改指针的指向
使用二级指针完成上诉题目
### 9.特殊指针
```c
1)万能指针
万能指针可以指向任意数据类型
万能指针不能直接解引用,必须先强转为某一数据类型
eg:
int a = 10;
void *p = &a;
printf("%d\n",*(int *)p);
2)const 修饰指针
eg1:
const int *p = &a;
*p = 100;//error 当前指针所指向地址空间中的内容不允许被修改
可以修改指针的指向,但不能修改指针所指向的地址空间中的内容;
eg2:
int * const p = &a;
*p = 100;//right
p = &a;//error 不能修改p的指向
可以修改指针所指向的地址空间中的内容,但不能修改指针的指向;
eg3:
const int * const p = &a;
*p = 100; //error 不能修改指向地址空间中的内容
p = &b; //error 不能修改p的指向
既不能修改指针所指向地址空间的内容,也不能修改指针的指向