指针
我们知道 数据存储在内存上 只有当前正在运行的数据会通过数据总线传输到cpu的寄存器上 激活算数逻辑单元 运算结束后又将数据传回内存 若要在内存中找到某个数据 要记录这个数据的存储位置 需要两个信息 即首地址和占用储存空间大小
取地址运算符&
- &运算对象
- 获取数据对象首地址和所需存储空间大小
取值运算符*
- *指针
- 根据指针中储存的首地址和空间大小找到并访问目标数据对象
#include <stdio.h>
int main()
{
int n = 1;
int* pn = &n;
printf("%d\n", *pn);//打印结果为1
return 0;
}
- 也可以通过指针修改数据对象
#include <stdio.h>
int main()
{
int n = 1;//n初始化为1
int* pn = &n;
*pn = 123;//通过指针修改数据对象
printf("%d\n", n);
printf("%d\n", *pn);//打印结果均为123
return 0;
}
指针类型
#include <stdio.h>
int main()
{
int n = 1;
int* pn = &n;
printf("pn=%u\n", pn);//打印结果为pn=336592272
return 0;
}
- 目标数据类型*变量名
- pn称为n的指针 或者说pn指向n
- 指针类型通过本身的值保存目标数据对象首地址
- 指针类型通过类型本身标记目标数据对象空间大小 如int*占4个字节
- 指针类型所占空间大小与编译器与配置有关 64位程序中占8字节 32位占4字节
强制转换指针类型
赋值后可存储首地址 但不同指针类型无法正确标记空间大小
指针运算
指针与整型加减
指针类型的值储存的是内存地址 内存地址为整型数据
整型数据可以被当成首地址但无法表示空间大小 所以无法将指针与整型直接运算 所以要将整型转换为对应的指针类型后再进行赋值运算
- (数据类型*)整型 将整型转换为指针类型
- sizeof(目标数据对象)被称为步长
- 指针类型加n 其首地址向后移动n*步长个字节 减n向前
- 取值运算符优先级高于算数运算符 所以使用括号 如*(n-1)
#include <stdio.h>
int main()
{
char* pc;
int* pn;
double* pd;
//将整型转化为对应的指针类型并给指针赋值
pc = (char*)100;
pn = (int*)100;
pd = (double*)100;
//运算
pc = pc + 1;
pn = pn + 1;
pd = pd + 1;
printf("pc=%u\n", pc);//结果为pc=101
printf("pn=%u\n", pn);//pn=104
printf("pd=%u\n", pd);//pd=108
return 0;
}
eg 通过指针与整型的加减来访问修改数据
#include <stdio.h>
int main()
{
int n1;
int n2;
//打印n1与n2的首地址 发现它们可能相差4(函数声明的变量在内存不一定是连续的)
printf("&n1=%d\n", &n1);
printf("&n2=%d\n", &n2);
//用指针获取n1的首地址并进行运算
int* p = &n1;
printf("p=%d\n", p);//p指向n1
printf("p+1=%d\n", p+1);//p前后移动4个字节恰好指向n2
//给n1和n2赋值
*p = 111;
*(p + 1) = 222;
//打印赋值后的n1与n2
printf("n1=%d\n", n1);//n1=111
printf("n2=%d\n", n2);//n2=222
return 0;
}
同类型指针做减法运算
使用数组来保证数据对象在内存中连续分布
- 指针类型与指针类型相减后 结果为两首地址差除以步长 意义为两元素中间隔了几个元素
#include <stdio.h>
int main()
{
int arr[10];
printf("&arr[0]=%d\n", &arr[0]);//&arr[0]=856685120
printf("&arr[5]=%d\n", &arr[5]);//&arr[5]=856685140
printf("&arr[5]-&arr[0]=%d\n", &arr[5]-&arr[0]);//&arr[5]-&arr[0]=5
return 0;
}
多级指针
就是指针套娃 指针的指针 如int*的指针为int**
#include <stdio.h>
int main()
{
int n = 123;
int* pn = &n;
int** pnn = &pn;
printf("pn=%d\n", pn);//pn=176160408
printf("*pn=%d\n", *pn);//*pn=123 还原一次
printf("&pn=%d\n", &pn);//&pn=176160400 由此可见 二级指针是可行的
printf("**pnn=%d\n", **pnn);//**pnn=123 还原两次
}
指针作为参数传递
形参与实参相互独立 比如下面的例子 打印结果与预想不同
#include <stdio.h>
//定义交换函数
void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a, b, temp;
a = 1;
b = 2;
printf("a=%d b=%d", a, b);//打印a,b原始值 a=1 b=2
swap(a, b);
printf("a=%d b=%d", a, b);//打印a,b交换后的值 a=1 b=2
return 0;
}
究其原因 修改参数的值但无法改变首地址 所以被调函数无法直接修改主调函数的变量 所以我们曲线救国 在主调函数中获取ab的指针 然后将指针作为参数传入被调函数 就可以在swap内部修改ab了 注 不是交换指针xy的值 而是交换目标数据对象ab的值 因此需要在指针前使用取值运算符*
#include <stdio.h>
//定义交换函数
void swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a, b, temp;
a = 1;
b = 2;
printf("a=%d b=%d\n", a, b);//打印a,b原始值 a=1 b=2
swap(&a, &b);//交换ab变量
printf("a=%d b=%d", a, b);//打印a,b交换后的值 a=2 b=1
return 0;
}
同理 scanf函数也是一样 scanf读取键盘的输入后 转换后储存在变量n中 但被调函数无法直接修改主调函数中的变量n 所以将变量n的指针传入scanf函数 通过指针使得被调函数间接修改主调函数中的变量
int n;
scanf("%d", &n);
但是指针除了保存数据对象首地址 还保存了数据对象占用储存空间大小 因此不同类型指针不能相互赋值
仅有首地址的指针类型void*
- void*仅保存首地址 不保存目标数据对象的空间大小 所以无法取值 且没有步长 无法进行加减运算
- 可接受任意类型指针的赋值 可以赋值给任意类型的指针(C++中后者不行)
int n;
void* p = &n;//int*赋值给void* 类型信息被丢弃 仅保存首地址
*p;//仅有首地址 未保存目标数据对象大小 无法取值
p + 1;//仅有首地址 没有步长 无法进行加减运算
从函数中返回指针
- 函数之间的变量是独立的
- 函数返回后 函数内部变量没有存在意义 因此会被回收
- 如果不想变量被回收 可以在变量前加关键词static
#include<stdio.h>
int* func()
{
static int n = 100;//关键词static让变量n不被回收
n++;//变量n自增
return &n;
}
int main()
{
int* p = func();//使用的是同一个地址上的变量 因此只需获取一次变量n的地址
printf("%d\n", *p);//打印结果为101
func();
printf("%d\n", *p);//打印结果为102
func();
printf("%d\n", *p);//打印结果为103
return 0;
}
从函数中返回多个变量
- 从函数中返回一个值可以使用返回值 返回多个值则需要像scanf函数一样 将指针作为参数传递到被调函数中
scanf("%d%d%d%", &a, &b, &c);//scanf从键盘读取三个整型并储存到变量a b c中
- 如果需要返回多个指针变量 则需要将二级指针作为参数传入函数
#include<stdio.h>
void func(int** a, int** b)
{
static int x = 100;
static int y = 200;
*a = &x;
*b = &y;
}
int main()
{
//两个指针初始化为空
int* a = NULL;
int* b = NULL;
func(&a, &b);//将指针的指针传入被调函数
if (a != NULL && b != NULL)//判断指针是否非0来确定函数func是否已经给指针赋值
printf("a=%d b=%d\n", *a, *b);
return 0;
}
- NULL是由#define NULL0定义的一个符号常量 意义是将指针初始化 即指针内保存的地址设置为0
void func(int* a)
{
static int x = 100;
*a = x;//int*转换为int 再赋值一个int给它
}
#include<stdio.h>
void func(int** a)
{
static int x = 100;
*a = &x;//int**转换为int* 再赋值一个int*给它
}
指针与数组
- 数组元素在内存中是连续的 第一个元素的首地址就是整个数组的首地址
- 数组名出现在表达式中 数组名将会转换为指向数组第一个元素的指针
- 规则2有两个例外1.对数组名使用sizeof时 结果为整个数组的大小 2. 对数组名使用取地址运算符时 结果为整个数组的首地址
获取数组首地址
- 使用第一个元素
- 使用数组名
#include <stdio.h>
int main()
{
int arr[10];
printf("&arr[0]=%d\n", &arr[0]);//使用第一个元素
printf("arr=%d\n", arr);//使用数组名
return 0;
}
访问数组元素
- 数组名[下标]
- *(数组名+偏移量)
数组名转换为指向第一个元素的指针 偏移量为指针指向地址与首地址相差几个元素 - 下标运算符也会展开为指针的形式 即A[k]=k[A]=*(A+k)
#include <stdio.h>
int main()
{
int arr[3] = {111,222,333};
printf("%d\n", arr[2]);
printf("%d\n", 2[arr]);
printf("%d\n", *(arr + 2));/打印结果均数组中的第三个元素333
return 0;
}
指针数组
- 即数组的元素类型为指针
- 数组声明:元素类型 数组名[元素个数][元素个数]...若元素类型中有[]移动到最右边
int *arr[3];
#include <stdio.h>
int main()
{
//初始化三个元素类型为整型的数组
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 11,22,33,44 };
int arr3[4] = { 111,222,333,444 };
//初始化一个元素类型为指针的数组
int* pToArr[3];//pToArr是元素类型为int* 元素数量为3的数组
pToArr[0] = arr1;//pToArr[0]类型为int* 因为arr1转化为首元素指针 指向arr1第1个元素 int[4]到int*
pToArr[1] = arr2;//pToArr[1]类型为int* 因为arr2转化为首元素指针 指向arr2第1个元素 int[4]到int*
pToArr[2] = arr3;//pToArr[2]类型为int* 因为arr3转化为首元素指针 指向arr3第1个元素 int[4]到int*
//用指针数组访问所有元素
for (int i = 0; i < 3; i++)
{
int** p = pToArr + i;//pToArr转换为首元素指针 即int*[3]转换为int**
for (int j = 0; j < 4; j++)
printf("%d ", *(*p + j));
//i=0时
//p指向pToArr的第一个元素 类型为int**
//*p表达式结果为pToArr[0] 指向arr1中的第一个元素 类型为int*
//*p+j分别指向arr1中的各个元素 类型为int*
//*(*p + j)表达式结果为arr1中的各个元素 类型为int
printf("\n");
}
}
数组指针
- 即指向数组的指针
- 声明指针:目标类型(*变量名)[元素个数]...若目标对象中有[]移动到最右边
int(*pInt10)[10]
移动
#include<stdio.h>
int main()
{
int B[2][3] = {
{1,2,3},
{1,2,3},
};
int(*pInt3)[3] = B;//B转换为指向首元素的指针 int[2][3]转换为int(*)[3]
printf("pInt3=%d\n", pInt3);//打印结果为pInt3=1169160328
printf("pInt3+1=%d\n", pInt3+1);//打印结果为pInt3+1=1169160340
//pInt3指向类型为int[3]类型的数组 所以指针pB步长为sizeof(int [3]) 即12
//通过pInt3的移动 可以使指针的目标数据对象在各个数组中切换
return 0;
}
取值
对int* 类型取值后会获得int 那么同理对pB 即int(*)[3]类型取值后会获得int[3]
*pInt3类型是数组 那么出现在表达式中 又会从int[3]转换为首元素指针 即int *类型
#include<stdio.h>
int main()
{
int B[2][3] = {
{1,2,3},
{1,2,3},
};
int(*pInt3)[3] = B;//B转换为指向首元素的指针 即int[2][3]转换为int(*)[3]
printf("B=%d\n", sizeof(B));//B的类型为int[2][3] 是数组 大小为24
printf("pInt3=%d\n", sizeof(pInt3));//pInt3类型为int(*)[3] 是指针 大小为8
printf("*pInt3=%d\n", sizeof(*pInt3));//*pInt3类型为int[3] 是数组 大小为12
int* pInt = *pInt3;//*pInt3是一个数组 转换为指向首元素的指针 int[3]到int*
printf("pInt=%d\n", pInt);//pInt=688911736
printf("pInt+1=%d\n", pInt+1);//pInt+1=688911740任何数组在内存中都是连续的
printf("pInt+2=%d\n", pInt+2);//pInt+2=688911744 *pInt+3可以访问B[1][0]
}
访问
访问数组有两种方法
两种方法等价
- 数组名[下标]
- *(数组名+偏移量)
[]是下标运算符 优先级高于一切运算符 运算时会展开为*(A+k) 所以这两种方式是数组名转换后指向的首元素指针进行运算 因此写成下面的形式更加通用 可通过这种方式无限套娃访问多维数组中的任意元素
- 指针[下标]
- *(指针+偏移量)
对数组取地址
对数组名使用&时 不会转换为首元素指针 而是直接取地址
对int取地址为int( *)类型的指针 同理对int[3]取地址为int( *)[3]类型的指针
#include<stdio.h>
int main()
{
int arr[3];
int(*parr)[3] = &arr;
printf("parr=%u\n", parr);// parr = 407894312
printf("parr+1=%u\n", parr+1);//parr + 1 = 407894324 parr是类型为int(*)[3]类型的指针 步长为12
int(**pparr)[3] = &parr;//对parr取地址 类型为int(**)[3]的二级指针 指向int(*)[3]的指针
return 0;
}
函数指针与数组
声明器优先级
- 括号()
- 函数声明的()与指数声明的[]优先级相同
- 指针声明的*
指向函数的指针
想要一个函数指针p指向名为print的函数
int print(char*pc)
- 首先它是指针 即(*p)
- 其次它指向函数 即(*p)(char *)
- 函数的返回值为int 即int (*p)(char *)
- 指针指向print函数 即int (*p)(char *) = print
- 更简便的方法
- 写出函数的声明 即int print(char*pc)
- 函数名替换成指针名 即int p(char*pc)
- 指针前加*并用()包括 即int (*p)(char *)
- 指针指向print函数 即int (*p)(char *) = print
- 类似数组在表达式中转换为指向首元素的指针 函数在表达式中转化为指向该函数的指针 因此函数print可用来初始化函数指针p
使用指向函数的指针
函数指针数组举例
void aaa()
{
printf("aaa\n");
}
void bbb()
{
printf("bbb\n");
}
void ccc()
{
printf("ccc\n");
}
以上函数的指针声明为void (*p)()
把四个函数指针组成一个数组arr
- arr是一个数组 所以要加上[] 即arr[3]
- 数组中元素是指针 且指针优先级低 所以无需括号 即arr[3]
- 指针指向函数 所以加上() 但*优先级低 所以要用()上一步声明包括起来 即(*arr[3])()
- 函数返回值为void 即void (*arr[3])()
把数组初始化
void (*arr[3])() = { aaa,bbb,ccc };
用循环遍历这个数组 并用指针调用
int main()
{
void (*arr[3])() = { aaa,bbb,ccc };
for (int i = 0; i < 3; i++)
(*arr[i])();//数组[]优先级高 arr[i]获得函数指针 *把函数指针转换为函数 再用()调用函数
return 0;
}
也可以省略* 把函数指针当成函数用( 函数指针后直接使用()调用函数
字符串与字符指针
字符串常量实际上是字符数组 而数组出现在表达式中 会转换为指向首元素的指针 指向第一个字符
字符串常量是常量 而常量不能被修改 所以字符串常量不可修改 但字符数组可修改
eg修改字符串hello为大写
//方法一 下标
#include<stdio.h>
int main()
{
char str[] = "hello";
puts(str);//hello
for (int i = 0; str[i] != '\0'; i++)
str[i] -= 32;
puts(str);//HELLO
}
//方法二 指针
#include<stdio.h>
int main()
{
char str[] = "hello";
puts(str);//hello
char* p = str;//若把指针写到下面循环的条件里 则指针为临时量 无法被赋值
while (*p != '\0')
{
*p -= 32;
p++;
}
puts(str);//HELLO
}
eg 将dlrowolleh倒转过来 还原为helloworld
#include<stdio.h>
int main()
{
char str[] = "dlrowolleh";
puts(str);
char* phead = str;
char* ptail = str;
while (*ptail)
ptail++;//在\0之前 内存地址自增
ptail--;//会增到\0 所以要减一下 *ptail指向h
while (phead <= ptail)//phead小于ptail时 进行交换 否则交换完毕跳出循环
{
char tmp = *phead;
*phead = *ptail;
*ptail = tmp;
phead++;
ptail--;
}
puts(str);
return 0;
}