文章目录
指针进阶
回顾
内存会划分为一个个的内存单元,每个内存单元都有一个独立的编号,而编号也称为地址。地址在C语言中也被称为指针。
指针(地址)需要存储起来——存储到变量中,这个变量就被称为指针变量。
int a = 10; int* pa = &a;
指针(地址)的大小是固定的4/8个字节(32位平台/64位平台)地址是物理的电线上产生
32位机器-32根地址线-1/0 32个0/1组成的二进制序列,把这个
二进制序列就作为地址,32个bit位才能存储这个地址,也就是需要4个字节才能存储,所以指针变量的大小就是4个字节
同理64位机器上,地址的大小是64个0/1组成的二进制序列,需要64个bit位存储,也就是8个字节。所以指针变量的大小是8个字节。
1.字符指针
int main()
{
char ch = 'w';
//如果我们要将ch的值修改,那么可以怎么做呢?
//法一
ch = 'a';
//法二
char* pc = &ch;
*pc = 'a';
return 0;
}
int main()
{
const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
值得注意,我们很容易误以为以上操作是将hello world
存放到字符指针pstr
中,实际上是将hello world
的首字符的地址放到了pstr
中。
int main()
{
char arr[] = "abcdef";
const char* p = "abcdef";
printf("%s\n", p);
printf("%c\n", *p);
return 0;
}
可以看出,指针存的是字符串的首字母。
之所以能打印出一行字符串,是printf的作用,找到了首字符,并以此地址往后寻找并打印。const放在
*
左边,修饰的*p
,放在*
右边,只限制p
。前者限制内容,后者限制地址。
例题
下列最终输出的是什么?
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
输出结果:
解析
2.指针数组
就是用来存放指针的数组。
int* arr1[10];//整型指针的数组
char* arr2[4];//一级字符指针的数组
char** arr3[5];//二级字符指针的数组
使用指针数组模拟实现二维数组(虽然没多大实际用处)
int main()
{
int arr1[] = { 1,2,3,4,5 };//arr1 - int*
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//指针数组
int* arr[3] = { arr1, arr2, arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
3.数组指针
类比一下:
整型指针-指向整型变量的指针,存放整型变量的地址的指针变量字符指针-指向字符变量的指针,存放字符变量的地址的指针变量
那么,
数组指针-指向数组的指针,存放的是数组的地址的指针变量
3.1 指针数组与数组指针的区别
int* p1[10];//指针数组
p1先跟
[10]
结合说明是数组([]
的优先级较*
更大),而int*
表明数组里的每个元素是指向整型的指针,p1
是数组名。所以是指针数组
int(*p2)[10];//数组指针
*p2
在括号内,所以优先看这个,为指针。
剩下的便是int [10]
,是一个整型数组,
所以是一个名为p2的指针,指向一个整型数组,共有10个单位所以是数组指针
3.2 &数组名VS数组名
数组名
总结
数组名是首元素的地址。
有2个例外
sizeof(数组名)
,这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)
计算的是整个数组的大小,单位是字节。&数组名
,这里的数组名表示整个数组,&数组名
取出的是整个数组的地址。除此之外,所有的地方的数组名都是数组首元素的地址
以下是两个例外的案例。
//数组名是首元素的地址。
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
结果如下:
//`sizeof(数组名)`,这里的数组名不是数组首元素的地址,数组名表示整个数组,`sizeof(数组名)`计算的是整个数组的大小,单位是字节。
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%d\n", sizeof(arr));
//如果是此处的arr是首字符的话
//那么结果将会是4/8
return 0;
}
结果如下:
&数组名VS数组名
请看这段代码:
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr);
return 0;
}
根据结果,我们会以为&数组名VS数组名
打印的地址是一样的,但确实如此吗?
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr + 1);
printf("%p\n", &arr + 1);
return 0;
}
结果如下:
分析:
arr
和&arr[0]
都是指首元素的地址
&数组名
,这里的数组名表示整个数组,&数组名
取出的是整个数组的地址。
3.3 数组指针的使用
先提醒一下,一般不是这么使用的。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//数组的地址,存储到数组指针变量
//int[10] *p = &arr;//err
int (* p)[10] = &arr;//int(*)[10]
//指针,指向一个10个单位的数组,每个单位存放一个整型
int* p2 = &arr;//err,该类型为存放整型指针
return 0;
}
如何打印?
//咱们一开始接触指针,会这么写
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
//使用数组指针呢?
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;//咱们需要的是一整个数组的地址,而不单单是首字符的地址。
//arr-->err
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*p) + i));
//*p-->*&arr-->arr
//除开*P,其他的格式:*(__+i)和第一个例子一样。
}
return 0;
}
数组指针怎么使用呢?一般在二维数组上才方便
二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一维数组
所以呢?二维数组其实是一维数组的数组
二维数组的数组名,也是数组名,数组名就是数组首元素的地址。
arr - 首元素的地址 - 第一行的地址 - 一维数组的地址 - 一个数组的地址!!!
没有使用数组指针
//二维数组传参,形参是二维数组的形式
void Print(int arr[3][5], int r ,int c)
{
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
Print(arr, 3, 5);
return 0;
}
使用了数组指针
void Print(int(*p)[5], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
Print(arr, 3, 5);
return 0;
}
4.数组参数和指针参数
4.1 一维数组传参
一维数组传参,形参的部分可以是数组,也可以是指针
示例
void test1(int arr[5], int sz)
{}
void test2(int* p, int sz)
{}
int main()
{
int arr[5] = { 0 };
test1(arr, 5);
test2(arr, 5);
return 0;
}
重难点判断题
#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
//以上写法都正确,其实数组名就是地址。
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
//以上写法都正确
//传给函数的arr2是数组名,首元素的地址,也就是指针的地址,那不就需要二级指针来接收一级指针的地址吗?所以写法正确
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };//指针数组
test(arr);
test2(arr2);
}
4.2 二维数组传参
一维数组传参,形参的部分可以是数组,也可以是指针
示例
void test3(char arr[3][5], int r, int c)
{}
void test4(char (*p)[5], int r, int c)
{}
int main()
{
char arr[3][5] = {0};
test3(arr, 3, 5);
test4(arr, 3, 5);
return 0;
}
重难点判断题
void test(int arr[3][5])//ok?
{}
//正确
void test(int arr[][])//ok?
{}
//错误
//形参部分,行可以省略,但是列不能省略
void test(int arr[][5])//ok?
{}
//正确
void test(int* arr)//ok?
{}
//错误
void test(int* arr[5])//ok?
{}
//错误
//形参是:指针数组
void test(int(*arr)[5])//ok?
{}
//正确
void test(int** arr)//ok?
{}
//错误
int main()
{
int arr[3][5] = { 0 };
test(arr);
//当你尝试将 arr 传递给 test 函数时,arr 是一个指向整数数组的指针
//数组名是首元素,一行的地址
//类型是 int (*)[5],表示一个指向包含5个整数的数组的指针。
}
总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以 不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。一行有几个元素很重要,但是行不重要
下一行从哪个元素开始,必须由一行有几个元素来决定
4.3 一级指针传参
示例
#include<stdio.h>
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
print(p, sz);
//一级指针p;传给函数
return 0;
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test(char* p)
{}
char ch = '2';
char* ptr = &ch;
char arr[] = "abcdef";
test(&ch);
test(ptr);
test(arr);
4.4 二级指针传参
示例
#include<stdio.h>
void test(int** ptr)
{
printf("num=%d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
当函数的参数为二级指针的时候,可以接受什么参数?
void test(char** p)
{}
char n = 'a';
char* p = &n;
char** pp = &p;
char* arr[5];
test(&p);
test(pp);
test(arr);
5.函数指针
函数指针 - 指向函数的指针
示例1
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = { 0 };
int(*pa)[10] = &arr;
/*printf("%p\n", &Add);
printf("%p\n", Add);*/
int(*pf)(int, int) = &Add;
//pf是函数指针变量(名)
//int(*)(int,int)是函数指针类型
return 0;
}
函数名
是函数的地址,
&函数名
也是函数的地址
示例2
void test(char* pc,int arr[])
{}
int main()
{
void (*pf)(char*, int[10]) = test;
return 0;
}
示例3
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf)(int, int) = Add;
int r = Add(3, 5);
printf("%d\n", r);
int m = (*pf)(4, 5);
printf("%d\n", m);
return 0;
}
示例4——很容易搞错的咧
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void
有难度的来了,两道题,判断一下是什么意思吧。
选自《C陷阱和缺陷》
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
代码1的思路过程
//代码1
(*(void (*)())0)();
先从数字0
下手,0
可以是整型(int
),也可以是指针(地址)(int*
)
0x0012ff40 既可以是
int
,也可以是int*
。
void(*p)()
指的是p是一个函数指针,指针变量p
指向的函数是一个没有参数,并且返回类型为void
(即无返回值)的函数。
void (*)()
是函数指针类型。
(void (*)())0
:就是把0
强制类型转换为一个函数指针
(*(void (*)())0)
:这是对上述函数指针的解引用操作,意味着从地址 0 中取出一个函数指针,然后调用该指针指向的函数。
(*(void (*)())0)()
:就是调用0地址处的这个函数
代码2的思路过程
//代码2
void (*signal(int , void(*)(int)))(int);
先论述如何使用typedef
简化这一串代码
typedef unsigned int uint;
typedef int* prt_t;
将
unsigned int
重定义为uint
,起到简写的作用。
注意,定义数组指针和函数指针就有点特殊了
typedef int(*p)[10] parr_t;//->err
typedef int(*parr_t)[10];//->right!
typedef int (*pf_t)(int,int);
int main()
{
uint u1;
prt_t p1;
int* p2;
return 0;
}
解析
//代码2
void (*signal(int , void(*)(int)))(int);
int main()
{
//signal 是一个函数声明
//signal 函数有2个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型
//该函数指针指向的函数有一个int类型的参数,返回类型是void
//signal 函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
void (* signal(int, void(*)(int) ) )(int);
return 0;
}