首页 > 其他分享 >指针总结

指针总结

时间:2023-03-09 19:32:10浏览次数:26  
标签:总结 arr int 地址 数组 printf 指针

这篇博客是我在学习了一段时间的指针后,做的一些总结,如有错误请严厉指正。

首先是我写的一篇关于指针的思维导图。

指针总结_二维数组

当然除了这些之外还有一个不是指针但是和指针有关的就是函数指针数组和指针数组,这两个都是数组但里面储存的元素都是指针。

然后我们先来解释什么是指针:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。当两个指针都指向同一个数组中的元素时两个指针相减就等于两个指针间的元素个数

整型指针也就是整型指针变量,这个变量里面存的就是一个整型的地址。

浮点型指针也就是浮点型指针变量,这个变量里面存的就是一个浮点数的地址。

然后我们来介绍字符指针,也即字符指针变量。里面存的也就是字符的地址。但是我们也可能会看到下面的这种写法。

int main()
{
const char* pstr = "hello .";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}

运行结果

指针总结_数组_02

从这个运行结果来看难道真的就是将这个字符串的地址传给这个指针了吗?

其实并没有这里只是将第一个字母的地址传给了pstr,然后在%s打印字符串的时候就从这个地址开始向后读取打印直达遇到\0.这种写法是不能通过解引用后修改数组的首元素的,因为这样写等于将一个常量字符串的首元素地址传给了这个字符指针,如果强制修改的话这个程序就会挂掉。而如果我们将这个字符串存入到数组里,然后运用一个字符指针指向这个数组那么这样我们就能通过指针来修改首元素了。

指针总结_数组名_03

从这个图里也能看到pstr里面存的也就是首元素地址。

那么从这一点也就有一个题

#include <stdio.h>
int main()
{
char str1[] = "hello world.";
char str2[] = "hello world.";
const char *str3 = "hello world.";
const char *str4 = "hello .";
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;
}

那么运行结果如下图

指针总结_二维数组_04

为什么会出现这种结果呢?首先这是两个数组所以计算机就分别分配了一片空间给这两个数组,这两个空间肯定是不相同的,然后将hello world.分别存进去,而数组名代表数组首元素的地址既然不在同一片空间,自然储存h的地址也就不同所以前面输出的就是not same。而下面的我们运用图来解释。

指针总结_数组名_05

因为是常量字符串不能被修改所以计算机并没有创建两个空间储存hello world,所以h也就不存在两个地址str1和str2里面存的就是一个相同的h的地址。所以下面打印的就是are same。

总结:对于字符数组来说我们要记住在一个字符指针储存一个常量字符串的时候,指针变量里面存的是第一个字符的地址。

接下来我们来看数组指针。

首先数组指针它是一个指针,只不过这个指针指向的是一个数组而已,在这里我们要将这个概念和指针数组分开,指针数组它是一个数组只不过里面储存的元素是一个个指针而已。

我们接下来来看这两个的代码写法

int main()
{
int arr[3] = { 0 };
int(*p)[3] = arr;//这就是一个数组指针的写法
int* p[10];//这就是一个指针数组有十个元素元素类型是int*
return 0;
}//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
//指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

下面我们来学习数组指针的使用

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}

上面的代码就是一个数组指针的使用,用于接受一个二维数组的地址(此时的地址就是二维数组的第一行的地址,因为这里不是在sizeof内部也不是&数组名)

然后下面我们就来学习一下数组的传参,为什么把这个放在指针一节,因为当我们传递数组的时候通常是将数组名传递,而数组名就是一个地址而接受地址自然就是用指针来接收。

我们先来看一维数组传参

#include <stdio.h>
void test1(int arr[])//这里我们传过来一个数组这里就用一个数组接收这自然是ok的
{
}
void test5(int arr[9])//这里和上面的一样
{
}
void test(int* arr)//这里也是可以的传过来的是数组名即数组首元素的地址首元素为整型所以用整型指针接受是可行的
{
}
void test2(int* arr[20])//传过来一个数组指针我们用一个数组指针接收自然是没有问题的
{
}
void test2(int** arr)//所以这里使用一级指针接受也是可以的
{
}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);//这里串过去的是一个指针数组的地址又因为数组名是数组首元素的地址这里也就相当于将int*的地址传过去了
}

总结对于一维数组传参,一定要仔细分析传过去的参数是什么类型的就如同第8行的那个函数,虽然传过来的是一个数组但数组名是首元素地址首元素有所一个整型所以用整型指针接受是完全可以的。

下面我们来看二维数组传参

#include<stdio.h>
void test(int arr[3][5])//传过来的是一个二维数组这里运用二维数组接收所以可行
{}
void test(int arr[][])//使用二维数组名字接收时行号是可以省略的(即二维数组前面括号里的数字)但是后括号(即二维数组的列号)里的不能省略,所以这样传参是不可行的
{}
void test(int arr[][5])//这里没有省略列号可行的
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//这里二维数组数组名相当于数组第一行的地址但是对于二维数组第一行的地址而言,它自身也是数组名就是数组第一行的数组名不再sizeof内部也不是取地址数组名也就相当于第一个元素的
//的地址即所以这里使用int* 类型的指针也可以接收
{}
void test(int* arr[5])//传过来的是二维数组第一行的地址所以这里用一个指针数组接收也是可以的
{}
void test(int(*arr)[5])//传过来的是一个数组的地址所以这里用一个数组指针接收也是可以的
{}
void test(int** arr)//地址传过来也就相当于一个指针变量将一个指针变量储存在一个一级指针里自然也是可以的
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}

总结:对于二维数组的数组名(不在sizeof内部也不是&数组名)就相当于第一行的地址,第一行我们又可以看成一个一维数组,那么对于一维数组而言数组名就相当于数组首元素的地址(也是在非特殊情况下)

指针总结_数组_06

下面我们来看一级指针传参

#include <stdio.h>
void print(int* p, int sz)//前面传递过来的是一个地址并且类型为int* 所以我们使用一个int* 类型的指针接收
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));//这里通过解引用p得到的就是arr数组的首元素的地址再通过这个地址打印出了这个数组里的值,因为数组的地址在内存里面
//是连续存放的所以p里面存的是首元素的地址加i跳过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]);//求出这个数组的元素个数
//一级指针p,传给函数
print(p, sz);
return 0;
}

所以最后的运行图

指针总结_数组名_07

下面我们来看二级指针传参。

#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;
}
////同理对于一个二级指针呢?
//它就能接收一个二级指针还有一个一级指针的地址通过下面的那个代码还有一个便是一个指针数组也是可以传过去
//并被二级指针接收的
#include<stdio.h>
void test(char** p)
{

}
int main()
{
char c = 'b';
char* pc = &c;
char** ppc = &pc;
char* arr[10];
//test(&pc);//二级指针能接收一级指针
//test(ppc);//二级指针也能接收一个二级指针
test(arr);//这里的arr是一个指针数组里面的元素是指针这里的数组名也就是数组首元素的地址而首元素是指针也就是一个指针的地址也就相当于传过去了一个装有指针地址的空间的地址
//这也就相当于将一个一级指针传过去了自然也是可以的
return 0;
}

下面我们就来学习函数指针

当我们写下一个函数时这个函数有没有地址呢?答案是当然有

#include<stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 3,b = 4;
int ret = Add(a, b);//我这里简单写了一个加法函数
printf("%d", ret);
return 0;
}

通过调式内存窗口就能看到Add函数的地址

指针总结_数组_08

既然是地址,那么就能被存起来,存着一个函数地址的指针也就被称为函数指针。

那函数指针的写法是这样的呢?

#include<stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 3,b = 4;
int ret = Add(a, b);//我这里简单写了一个加法函数
printf("%d", ret);
int (*p)(int, int) = &Add;//这个p就是一个函数指针变量,p先于*结合代表这是一个指针后面的括号又表示这是一个函数指针传入参数的类型为int 和 int
//返回值也是int
return 0;
}

函数指针的使用

#include<stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 3,b = 4;
int (*p)(int, int) = &Add;//这个p就是一个函数指针变量,p先于*结合代表这是一个指针后面的括号又表示这是一个函数指针传入参数的类型为int 和 int
//返回值也是int
int ret = (*p)(2,3);//通过解引用p得到函数地址再将参数传递过去最终返回计算值
int ret =Add(3,4);
int sed = p(2, 3);//这两种方法都可以调用函数指针但第二种为什么可以呢
//这里我们可以将这种写法类比成直接使用函数指针名的那种方法因为我们直接运用函数名调用函数的时候
//函数名里存的就是函数的地址这里我们的指针变量也是存了函数地址所以可以不解引用直接使用
printf("%d %d", ret,sed);
return 0;
}

运行截图

指针总结_数组名_09

那么我们继续类比之前既然有指针数组,那么对于函数指针的话有没有数组呢?答案当然是肯定的。

这就是我们下面要解释的函数指针数组


#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{

int (*p[5])(int, int) = {Add,Sub,Mul,Div};//前面的代码理解为首先p与括号结合代表这是个数组然后其它的则代表数组里的元素是一个函数指针,指针指向的函数参数为两个整型返回值为整型
//这也就是一个函数指针数组
for (int i = 0; i <= 3; i++)
{
printf("%d \n",p[i](8, 5));//通过p里的地址使用函数当i为0的时候打印的就是8+5。1就是8-5,2就是8乘5,3就是8除以5
}
return 0;
}

而通过函数指针数组我们也能完成一个简单的整型计算器

#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("***********************\n");
printf("******简单计算器*******\n");
printf("*****1.Add 2.Sub ******\n");
printf("*****3.Mul 4.Div*******\n");
printf("****0.exit *********\n");
}
int main()
{
int(*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
int input = 0;
menu();
do {
printf("请选择\n");
scanf("%d", &input);
if (input > 0 && input <= 4)
{
printf("请输入两个操作数\n");
int x = 0, y = 0;
scanf("%d %d", &x, &y);
printf("%d\n", p[input](x, y));
}
else if (input == 0)
{
printf("退出计算机\n");
}
else
printf("选择非法\n");
} while (input);
return 0;
}

在前面我们只知道有指向数组的指针那么同样的这里也就还有指向函数指针数组的指针,

写法

int Add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a / b;
}
#include<stdio.h>
int main()
{
//我们这里先写一个函数指针数组
int (*p[5])(int, int) = { Add,sub };
//而指向函数指针数组的指针只需要在上面的基础上修改就是了
int (*(*p)[5])(int, int);
return 0;
}

关于它的使用我还未彻底了解清楚就不再这里说了。

在最后我这里还有指针的三道题

也想在这里写了

////题目1
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}//这道题的答案是1,坑人的点在于数组初始化里面不是{}而是(),所以就导致了()构成了一个逗号表达式,而逗号表达式从左到右依次计算有效值是最后一个表达式的结果,所以这个数组其实相当于a[3][2]={1,3,5,0.0.0}
//然后a[0]相当于是第一行的数组名然后存入到了p中,p0就相当于是打印a[0][0]所以打印的是1
//题目2
#include<stdio.h>
int main()
{
int a[5][5];
int(*p)[4];//这是一个数组指针
p = a;//因为这里并不是在sizeof内部或是取地址数组名所以这里的a就代表第一行第一列那个元素的地址。但是若是要接受这个地址的话p的类型应该是int(*p)[5]
//但是这里不是所以就会出现问题
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//由图片我们就能知道这两个地址相减的值就是-4,然后%d打印的自然就是-4但是%p打印就是将-4的补码当作地址打印
//-4的原码10000000000000000000000000000100
//反码 11111111111111111111111111111011
//补码 11111111111111111111111111111100
//这个补码被认为是地址
//4个2进制数转化为1个16进制数就是FFFFFFFFFFFFFFFC
return 0;
}

题目二理解图

指针总结_二维数组_10

题目三

//题目三
#include<stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);//这里就是将aa这个二维数组的整个地址取了出来+1跳过了这一整个二维数组,然后再将其转化为整型地址
int* ptr2 = (int*)(*(aa + 1));//这里每有&也没有在sizeof内部代表的就是二维数组第一行的地址加1跳过的就是第一行,然后再将其转化为整型地址
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//所以这里ptr-1向前前进了一个整型的大小所以前面打印的就是10,后面就是从6的地址向前1位所以打印的就是5
return 0;
}

题目四

//题目4
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };//这里就是一个指针数组这个数组的元素就是指针,而对于字符串这个数组里储存的就是第一个字母的地址
char** pa = a;//这里的a不在sizeof内部也不是取地址数组名所以这里就是将w的地址给了pa
pa++;//pa加了一个1自然也就从指向第一个元素变成了第二个元素(需要注意这里不是从w跳到了o)而是从work跳到了at
printf("%s\n", *pa);//这里解引用pa之后得到的就是a[2]的地址通过a[2]里面储存的a的地址向后打印字符所以是at
return 0;
}//这里便使用了字符指针只会储存字符串第一个字符地址的特点

题目5

#include<stdio.h>
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };//这个cp是一个二级指针数组用来存储指针的地址
char*** cpp = cp;//cpp就是一个三级指针里面储存着二级指针的地址
printf("%s\n", **++cpp);//这里先对cpp加一即将cpp里面储存的地址加一然后第一次解引用自然就通过这个地址找到了c+2的那个空间在对c+2里的地址解引用自然也就得到了POINT,此时cpp的指向也加1了
printf("%s\n", *-- * ++cpp + 3);//这里我们一步步的来解决先是cpp再一次加其中的地址就指向了存着c+1地址的那个空间然后解引用自然就到了存着NEW那个地址的空间
//然后是这个空间再次--自然就到了存着ENTER那个地址的空间这个地址解引用就到了E的位置那个位置之后+3所以最后这里打印的就是ER因为解引用后得到的是E,+3自然也就到了E然后从此开始打印所以最后打印的
//就是ER
printf("%s\n", *cpp[-2] + 3);//这里的*cpp[-2]可以理解为**(cpp-2)+3那么首先就是将cpp里面储存的地址减去2自然也就让cpp里面的地址指向了c+3然后解引用也就到了c+3的那片空间里面储存的地址是
//是指向F的再次解引用自然就得到了F然后从F开始向后加三后打印所以最后得到的就是ST
printf("%s\n", cpp[-1][-1] + 1);//这里的cpp[-1][-1]可以理解为*(*(cpp-1)-1)+1。那么首先就是对cpp里面的地址减去1使其指向了c+2.这时候解引用自然也就到了c+2的那片空间从c+2的那片空间减去1
//也就到了c+1的那片空间而这片空间里储存的地址指向的就是NEW,这时候解引用也就到了N处在+1跳过N打印所以这里最后打印的就是EW。
}

题目5的理解图

指针总结_二维数组_11

这便是我在学习完指针后的感悟。感觉写的不好,请见谅。

如果有错误,请严厉指出我一定改正。


标签:总结,arr,int,地址,数组,printf,指针
From: https://blog.51cto.com/u_15838996/6111135

相关文章

  • c语言指针和传引用
    指针可能已经会了,只写一个例子吧形式参数和实际参数首先一个前置知识就是一个实际参数和形式参数实际参数:真实传给函数的参数,叫实参。形式参数是指函数名后括号中的变量......
  • 3月09日课后总结
    3/09课后总结贪婪匹配与非贪婪匹配""" 正则表达式都是默认贪婪匹配 如:字符串<abc>123<abc> 正则表达式<.*> 则会匹配到<abc>123<abc> 非贪婪匹配则是<.*?> 匹配到<......
  • 今日总结
    输入a,b班的名单,并进行如下统计。输入格式:第1行::a班名单,一串字符串,每个字符代表一个学生,无空格,可能有重复字符。第2行::b班名单,一串字符串,每个学生名称以1个或多个空格分......
  • 【学习总结】计算机组成原理
    参考笔记:CSDN:从前慢-计算机组成原理:B站王道考研视频下发现的CSDN:从前慢-操作系统END......
  • mvc-学习javaweb项目一后部分知识总结
    资料来源于:B站尚硅谷JavaWeb教程(全新技术栈,全程实战),本人才疏学浅,记录笔记以供日后回顾总体内容是P39-P45,这边只放了一个链接。视频链接知识点总述1.最初的做法......
  • 3月9日总结
    作为一枚合格的代码贡献者,时常需要跟踪自己或者团队代码的变更,那么就很有必要了解并掌握一些软件代码版本管理工具或者系统,比如Git、SVN、CVS、VSS等。版本管理工具比较......
  • 模拟总结2
    这回寄了,只拿了三分T1貌似做过啊,但是我往八个方向扩展貌似写挂了,前缀和也没想到qwqT2真心不会T3没有看明白规律...T4暴力写挂了T5部分分写挂了,没想到用堆T6还......
  • 双指针:滑动窗口
    lc2379得到k个黑色快的最少operate说实话,滑动窗口还是见少了,知道有这个东西,一直没总结,刚看到题,自己还是很懵逼的,以为是dp,但是是简单题,都说用滑动窗口做,才有思路大概思......
  • 3月08日课后总结
    3/08课后总结绝对导入和相对导入#程序中多个模块之间导入的时候始终以执行文件所在的路径作为基准1.绝对导入: #始终以执行文件所在的环境变量sys.path为基准2.相......
  • 大学课程总结
    2019-2020学年第1学期1.程序设计基础(JAVA语言)良好学习java基本语言,那时候真的是年少不知时间宝贵,两个班到处跑2.程序设计基础课程实践(JAVA)优秀刷acm和用java实现......