指针
为什么要使用指针
- 函数的值传递,无法通过调用函数,来修改函数的实参;
- 被调用的函数需要返回更多的“返回值”给调用函数;
- 减少值传递带来的额外开销,提高代码执行效率。
指针的定义
定义形式: 指针类型 *指针变量名;
#include <stdio.h>
#include <Windows.h>
int main(){
int age = 0;
char ch = 'a';
int *p; // 定义了一个整形指针p,它可以指向整形变量
char *c; // 定义了一个字符型指针c,他可以指向字符型变量
p = &age; // 指针p指向变量age,p的值就是age的地址
c = &ch;
printf_s("输入:");
scanf_s("%d", p);
printf_s("值为:%d\n", age);
system("pause");
return 0;
}
指针的初始化
#include <stdio.h>
#include <Windows.h>
int main(){
int age = 10;
int *p = &age; // 定义了一个整形指针p,并用age初始化该指针
system("pause");
return 0;
}
指针的访问
访问指针
访问(读、写)指针变量本身的值,和其他普通变量的访问方式相同。
#include <stdio.h>
#include <Windows.h>
int main(){
int age = 10;
int *p = &age; // 定义了一个整形指针p,并用age初始化该指针
// 采用10进制方式打印指针的值,不推荐使用该方式
printf_s("10进制方式打印指针的值:%d\n", p);
// 采用16进制方式打印指针的值
printf_s("访问指针:0x%p\n", p);
printf_s("访问指针:0x%X\n", p);
printf_s("访问指针:0x%x\n", p);
system("pause");
return 0;
}
访问指针所指向的内容
使用解引用运算符(*)
#include <stdio.h>
#include <Windows.h>
int main(){
int age = 10;
int *p = &age; // 定义了一个整形指针p,并用age初始化该指针
int i = *p; // *p表示读取p所指向的变量的值,等同于int i = age;
printf_s("i=%d\n", i);
*p = 20; // 等同于 age = 20;
printf_s("修改后,age的值为:%d,*p的值为:%d\n", age, *p);
system("pause");
return 0;
}
空指针和坏指针
空指针
什么是空指针
就是值为0的指针。任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。
int *p = 0;
int *p = NULL; // 推荐
空指针的使用
- 指针初始化为空指针,目的:避免访问非法数据;
int *p = NULL;
- 指针不再使用是,可以设置为空指针;
int *p = &age;
*p = NULL;
- 表示这个指针还没有具体的指向,使用前进行合法性判断。
int *p = NULL;
// ......
if (p) { //p 等同于 p!=NULL
//指针不为空,对指针进行操作
}
坏指针
int *select; //没有初始化
// 情形一 没有赋值,直接访问
printf("选择的房间是: %d\n", *select);
// 情形二
select = 100;
printf("选择的房间是: %d\n", *select);
const关键字
(1) const int *p; //锁值
(2) int const *p; //同上,锁值
(3) int *const p; //锁址
即:const 在前面 锁值,在后面 锁址
指针的算术运算
注: p++ 的概念是在 p 当前地址的基础上 ,自增 p 对应类型的大小,
也就是说 p = p+ 1*(sizeof(类型))
#include <stdio.h>
#include <Windows.h>
int main(){
int ages[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
int *p = ages; // 指针指向数组时不用加取地址符
for(int i = 0; i < sizeof(ages)/sizeof(ages[0]); i++) {
printf_s("%d ", (*p)++);
}
system("pause");
return 0;
}
二级指针
二级指针也是个普通的指针变量,只是它里面保存的是一级指针的地址。
定义
#include <stdio.h>
#include <Windows.h>
int main(){
int age = 10;
int *p1 = &age; // 一级指针,保存age的地址
int **p2 = &p1; // 二级指针,保存p1的地址
system("pause");
return 0;
}
二级指针的用途
一级指针可以将外部变量通过参数“带入”函数,但是没办法将内部变量“带出”函数
二级指针不仅可以将外部变量通过参数“带入”函数,还可以将内部变量“带出”函数
// 二级指针使用场景
#include <stdio.h>
#include <Windows.h>
void pointorUser(int **p) {
static int i = 10;
*p = &i;
}
int main(){
int *p = nullptr;
pointorUser(&p);
if(p){
printf_s("存在!指向的值为:%d\n", *p);
}else{
printf_s("不存在!\n");
}
system("pause");
return 0;
}
多级指针的定义及使用
可以定义多级指针指向次一级指针
// 多级指针的定义及使用
#include <stdio.h>
#include <Windows.h>
void pointorUser(int **p) {
static int i = 10;
*p = &i;
}
int main(){
int age = 10;
int *p1 = &age;
int **p2 = &p1;
int ***p3 = &p2;
int ****p4 = &p3;
int *****p5 = &p4;
printf_s("p1取年龄:%d\n", *p1);
printf_s("p2取年龄:%d\n", **p2);
printf_s("p3取年龄:%d\n", ***p3);
printf_s("p4取年龄:%d\n", ****p4);
printf_s("p5取年龄:%d\n", *****p5);
system("pause");
return 0;
}
指针和数组
指针表示法和数组表示法
数组完全可以使用指针来访问,days[3]和*(days+3)等同
#include <stdio.h>
#include <Windows.h>
int main(){
int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int arrSize = sizeof(days)/sizeof(days[0]);
// 数组表示法
printf_s("数组表示法:\n");
for(int i = 0; i < arrSize; i++) {
printf_s("%d月有%d天!\n", i + 1, days[i]);
}
// 指针表示法
printf_s("指针表示法:\n");
for(int i = 0; i < arrSize; i++) {
printf_s("%d月有%d天!\n", i + 1, *(days + i));
}
system("pause");
return 0;
}
存储指针的数组
定义形式:数据类型 *指针数组名[数组长度];
#include <stdio.h>
#include <Windows.h>
/*******************************************************
* 项目需求:查找数组中排名前3的身高
********************************************************/
void findHeight(int arr[3][3], int *p[3]) {
int min = 0;
for(int i=0;i<3;i++) p[i] = &min; // 把所有身高置为0
// 查找最高身高
for(int i=0;i<3; i++){
for(int j=0;j<3;j++){
if(arr[i][j] > *(p[0])) p[0] = &arr[i][j];
}
}
// 查找第二身高
int max = *(p[0]);
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
if(arr[i][j] > *(p[1]) && arr[i][j] < max){
p[1] = &arr[i][j];
}
}
}
// 查找第三身高
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
if(arr[i][j] > *(p[2]) && arr[i][j] < *(p[1])){
p[2] = &arr[i][j];
}
}
}
}
int main(){
int heights[3][3] = {
{ 173, 166, 158 },
{ 163, 155, 182 },
{ 154, 162, 172 }
};
int *p[3]; // 定义一个有3个元素的指针数组,数组中每个都是一个指针变量
findHeight(heights, p);
if(p[0]) {
for(int i=0;i<3;i++){
printf_s("第%d身高是:%d\n", i+1, *(p[i]));
}
}
system("pause");
return 0;
}
指针和二维数组
指向数组的指针
定义:数据类型 (*数组名)[数组长度);
访问方式:参考指针和数组之指针表示法和数组表示法
#include <stdio.h>
#include <Windows.h>
void findMin(int arr[3][3], int **min){
// 定义了一个指针,该指针指向3个int成员
int (*p)[3] = nullptr;
*min = &arr[0][0];
printf_s("min值:%d\n",*min);
for(int i = 0;i < 3; i++) {
p = &arr[i];
for(int j = 0;j < 3; j++) {
printf_s("%d ", *((*p)+j));
if(*((*p)+j) < **min) *min = (*p)+j;
}
printf_s("\n");
}
}
// 查找最矮身高
int main(){
int heights[3][3] = {
{ 173, 166, 158 },
{ 163, 155, 182 },
{ 154, 162, 172 }
};
int *min = nullptr;
findMin(heights, &min);
if(min) {
printf_s("找到最矮身高,是:%d\n", *min);
}
system("pause");
return 0;
}
使用普通指针访问二维数组
#include <stdio.h>
#include <Windows.h>
// 使用普通指针访问二维数组,指针法访问
void findMin(int *arr, int size, int **min){
*min = arr;
for(int i=0;i<size;i++) {
if((*(arr+i)) < **min) {
*min = arr+i;
}
}
}
// 查找最矮身高
int main(){
int heights[3][3] = {
{ 173, 166, 158 },
{ 163, 155, 182 },
{ 154, 162, 172 }
};
int *min = nullptr;
findMin(&heights[0][0], sizeof(heights)/sizeof(heights[0]), &min);
if(min) {
printf_s("找到最矮身高,是:%d\n", *min);
}
system("pause");
return 0;
}
数组与指针的区别
数组:用于储存多个相同类型数据的集合。
指针:指针是一个变量,但是它和普通变量不一样,它存放的是其它变量在内存中的地址。
-
赋值
数组:只能单个赋值或拷贝
指针:可以相互赋值 -
范围
数组:有效范围就是其空间的范围,数组名使用下表引用元素,不能指向别的数组
指针:可以指向任何地址,但是不能随意访问,必须依附在变量有效范围之内 -
sizeof
数组:
数组所占内存:sizeof(数组名)
数组个数:sizeof(数组名)/sizeof(数组第一个元素或者数据类型)
指针:
32位系统下占4个字节
64位系统下占8个字节 -
指针数组和数组指针
(1)指针数组
int age1 = 15;
int age2 = 20;
int *p[2];//定义一个有两个元素的指针数组,每个元素都是一个指针变量
p[0] = &age1;
p[1] = &age2;
(2)数组指针
int (*p)[3]; //定义一个指向三个成员的数组的指针
访问元素的两种方式:
int A[4][3]={{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172}};
p = &A[0];
数组法: (*p)[j]
指针法: *((*p)+j)
- 传参
数组传参时,会退化为指针。
退化的意义:C 语言只会以值拷贝的方式传递参数,参数传递时,如果只拷贝整个数组,效率会大大降低,并且在参数位于栈上,太大的数组拷贝将会导致栈溢出。因此,C 语言将数组的传参进行了退化。将整个数组拷贝一份传入函数时,将数组名看做常量指针,传数组首元素的地址。
#include <stdio.h>
#include <Windows.h>
// 一维数组传参,不指定数组大小
void printA(int arr[], int size) {
for(int i = 0; i < size; i++) {
printf_s("%d ", arr[i]);
}
printf_s("\n");
}
// 一维数组传参,指定数组大小,可移植性低
void printB(int arr[8]){
for(int i = 0;i < 8; i++) {
printf_s("%d ", arr[i]);
}
printf_s("\n");
}
// 一维数组传参退化,用指针进行接收,传的是数组的首地址
void printC(int *arr, int size) {
for(int i = 0;i < size; i++) {
printf_s("%d ", arr[i]);
}
printf_s("\n");
}
// 查找最矮身高
int main(){
int heights[] = { 156, 168, 177, 158, 162, 175, 180, 179 };
// 一维数组传参,不指定数组大小
printf_s("一维数组传参,不指定数组大小\n");
printA(heights, sizeof(heights)/sizeof(heights[0]));
// 维数组传参,指定数组大小,可移植性低
printf_s("维数组传参,指定数组大小,可移植性低\n");
printB(heights);
// 一维数组传参退化,用指针进行接收,传的是数组的首地址
printf_s("一维数组传参退化,用指针进行接收,传的是数组的首地址\n");
printC(heights, sizeof(heights)/sizeof(heights[0]));
system("pause");
return 0;
}
void型指针
void => 空类型
void * => 空类型指针,只存储地址的值,丢失类型,无法访问,要访问其值,我们必须对这个指针做出正确的类型转换,然后再间接引用指针。
常见使用场景:函数传参
注意:
- 所有其它类型的指针都可以隐式自动转换成 void 类型指针,反之需要强制转换。
- 不可以进行算术运算。
- 任何类型可以隐式转换成void类型指针,但是void类型指针必须强制类型转换成指定类型。
#include <stdio.h>
#include <Windows.h>
// 一维数组传参,不指定数组大小
void printA(int arr[], int size) {
for(int i = 0; i < size; i++) {
printf_s("%d ", arr[i]);
}
printf_s("\n");
}
// 一维数组传参,指定数组大小,可移植性低
void printB(int arr[8]){
for(int i = 0;i < 8; i++) {
printf_s("%d ", arr[i]);
}
printf_s("\n");
}
// 一维数组传参退化,用指针进行接收,传的是数组的首地址
void printC(int *arr, int size) {
for(int i = 0;i < size; i++) {
printf_s("%d ", arr[i]);
}
printf_s("\n");
}
// 查找最矮身高
int main(){
char c = 'a';
void *p = &c; // 可以隐式转换成void型指针
char *p1 = (char *)p; // 必须强制类型转换
printf_s("%c\n", *(char *)p);
system("pause");
return 0;
}
函数指针
定义方法:返回值类型 (*p)(函数参数列表);
调用方式:贝尔实验室的C和UNIX的开发者采用(*p)(函数参数列表),而伯克利的UNIX推广者却采用p(函数参数列表),ANSI C 兼容了两种方式。
#include <stdio.h>
#include <Windows.h>
int compareInt(const void *a, const void *b) {
return ((*(int *)a) - (*(int *)b));
}
int compareChar(const void *a, const void *b) {
return ((*(char *)a) - (*(char *)b));
}
// 查找最矮身高
int main(){
int (*p)(const void *a, const void *b); //定义了一个函数指针
int x = 10, y = 15;
p = &compareInt;
int ret = p(&x, &y);
printf_s("%d与%d相比,%d大!\n", x, y, ret>0?x:y);
char c1 = 'a', c2 = 'z';
p = &compareChar;
ret = p(&c1, &c2);
printf_s("%c与%c相比,%c大!\n", c1, c2, ret>0?c1:c2);
system("pause");
return 0;
}