文章目录
一.指针初阶的补充
二.数组指针(指针进阶开始)
三.数组传参和指针传参
四.函数指针
五.有趣的代码,常量指针与指针常量
前言
指针是c语言里面很重要的模块,我们应该熟练这些指针的运用,这样才可以在后面的数据结构和结构体里面更好的掌握
内容
一.指针初阶的补充
1.指针的大小
指针的大小是固定4或者8个大小,(32位和64位)无论什么类型的指针,跟他们的单单指类型的大小还是有区别的
2.内存含义
对于内存单位的编号叫做地址,这个也叫做指针,这个编号是不用额外去存储的,是硬盘自己生成的
3.指针的定义
int a=0;int *p=&a;(定于的时候*这个表示为这个变量是指针(指针变量)其余的地方基本都是解引用的意思)我们平时所说的指针通常是指针变量,如这里的p
指针的本质就是地址(对于32位机器有2的32次方个地址,以此类推这个64位机器)
每一个地址标识为一个字节,那我们就可以给(2的32次方byte==2的32次方/1024KB==...==4GB)
一个字节为8个bit,32位数储存4个字节,64位储8个字节,指针字节
4.指针类型的意义
指针任何类型的字节都是一样的,那还要分类型干什么呢?有什么意义呢?
意义1:
#include<stdio.h>
int main()
{
int a = 0x11223344;
char* pc = (char*)&a;
*pc = 0;
return 0;
}
我们用vs2019测试一下,下面是a地址的改变
最初状态
改变后的状态
这个说明这个类型虽然字节数相同,但是他们决定着当你要改变这个值的时候改变的访问几个字节
意义2:
当我们用指针进行常数的相加减的时候,类型决定了他可以跳过几个字节访问下一个元素
#include<stdio.h>
int main()
{
int a = 0x11223344;
char* pc = (char*)&a;
int* pa = &a;
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("%p\n", pc);
printf("%p\n", pc + 1);
}
我们用vs2019运行一下
这里就可以看出类型决定着访问的字节数
5.野指针
野指针就是你这个指针不知道指向那个地方,指向的位置未知,这就可能会出想非法访问错误
一般野指针出现的情况:1未初始化 2数组越界 3指向的空间被释放(如malloc分配的空间释放后指针没有给予NULL)
第三个情况还有一个常见的错误案例:
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
}
这个代码看似没有什么问题,其实我们返回a的地址的时候,这个函数调用完,这个函数其实也被暂时被释放了,所以a的地址其实是找不到的,所以这个p指针也是一个野指针
为了避免这些错误的案例,有一下几种习惯可以尽量避免野指针:
1.int a =10; int *p =&a; 给p指向一个值
2.int *p=NULL; 给p赋予一个空指针
6.指针的运算:
指针与整数的运算:
指针通过类型来判定这个加减整数是怎样移动的,移动多少,我们主要来看这个这个,比较重要
*p++和(*p)++的区别:
*p++是先自增,自增完,内存往后面移动一格,然后移动完之后就解引用
(*p)++是是把p解引用之后,然后对于那个值进行++
指针与指针的运算:
指针-指针的运算结果得到的是指针与指针之间的元素
注:不是所有的指针相减都是可以得到元素个数的,必须是同一块空间的指针进行指针的运算,才可以有意义
我们可以模拟实现一个strlen函数来试试这个方法:
#include<stdio.h>
int my_strlen(const char*p)
{
const char* pa = p;
while (*p != '\0') {
p++;
}
return(p - pa);
}
int main()
{
int len = my_strlen("abcdef");
printf("%d", len);
return 0;
}
我们可以看到这个函数是计算出为2,则可以证明这个方法是可以的
指针的关系运算:
标准规定:允许指针指向数组元素的指针与指向数组最后一个元素后面那个内存位置的指针比较,但是不可以与数组的第一个元素进行比较
7.指针与数组
数组可以通过指针来访问*(p+i) arr[i]->*(arr+i)
8.指针数组(存放地址的数组)
初识指针数组(我们先来一个简单的代码来认识一下这个东西):
#include<stdio.h>
int main()
{
int a = 10; int b = 20; int c = 30;
int* p[3] = { &a,&b,&c };
for (int i = 0; i < 3; i++) {
printf("%d ", *p[i]);
}
return 0;
}
我们看到用vs测试是可以运行的,这个指针数组都是存储这个地址的
我们认识到了这个指针数组是怎么样的接下来我们用这个来模拟一下二维数组
#include<stdio.h>
int main()
{
int a[4] = { 1,2,3,4 };
int b[4] = { 2,3,4,5 };
int c[4] = { 3,4,5,6 };
int* d[3] = { a,b,c };
int i = 0; int j = 0;
for (i = 0; i < 3; i++) {
for (j = 0; j < 4; j++) {
printf("%d ", d[i][j]);
}
printf("\n");
}
}
这里的d[i]是指a,b,c,然后后面加一个[j]就是a[j]所以就可以模拟实现这个二维数组了
二.数组指针
指针数组的定义:int (*p)[10]=&arr;
如如果把p+1的话是一次性进行跳过10个int类型的字节数
这里我们要了解一个方法,圈画法来解释写乱七八糟的指针类型
这个是我们拿出几个例子来讲的,我们要学会圈画法,这里第三个可能不太详细,那个指向的类型数组后面应该还有数组里面的元素
当我们定义数组指针的时候,难免会出现些错误
常见的错误:
1,数组指针设有下角标:
int p[3]={1,2,3}; int (*p1)[3]=&p; int(*p2)[]=&p;
第一个数组指针是对的,第二个不对,因为这个没有大小,所以系统会默认为0,这个时候就会出现错误
运用:
int i=0; for(i=0;i<3;i++){ printf("%d ",*(*p+i)); }
*p指的是他指向的那个数组的地址,然后进过i来进行解引用来打印各个元素。
数组指针一般都用在二维数组
怎么使用呢,我们来看一个代码
#include<stdio.h>
void print(int(*p)[4], int hang, int lie)
{
int i = 0; int j = 0;
for (i = 0; i < hang; i++) {
for (j = 0; j < lie; j++) {
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int a[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
print(a,3,4);
return 0;
}
这里最难理解的就是这个*(*(p+i)+j)这个代码,这个就是解开p+i是第i行的地址拿到哪一行的数组名,然后外面是对于这个数组名,这个数组名本身就是一个地址,然后对于地址进行+i来移动,然后在解引用,解开这个地址,就是那一行那一列的值了
存放数组指针的数组:定义:int(*arr[10])[5];这个是这个数组里面有10个指针元素,这10个指针元素每个指向的都是含有5个int类型的数组
回顾传参:
一维数组的传参:
int main()
{
int arr[10] = { 0 };
int* arr1[10] = { 0 };
//对于arr的传参:
void test(int arr[10] )
void test(int arr[])
void test(int *arr)
//对于arr1的传参
void test(int* arr1[10])
void test(int* arr1[])
void test(int* *arr1)
}
二维数组传参:
int main()
{
int arr[10][10] = { 0 };
void test(int arr[10][10])
void test(int arr[][10])
void test(int arr[][])//这个是错误的
void test(int (*arr)[10])
void test(int **arr)//这个是错误的,二级指针是专门用来存放一级指针变量的一个地址的,放一个一维数组肯定不行
}
一级指针传参与二级指针传参:
很简单,这里就不说了,就一个test(int*a)test(int**a)没了
函数指针
通俗的来讲就是指向一个函数的指针(函数名与&函数名都是函数的地址,与数组名是有区别的)
函数指针的定义与使用:
int (*pf)(int x,int y)=&Add或者Add
调用的时候int ret=pf(2,3)或者int ret=(*pf)(2,3)也是可以的,但是int ret =*pf(2,3)是不一样的,这个是把函数pf返回回来的值进行解引用操作
对于函数指针的用法:
#include<stdio.h>
int add(int x, int y) {
return x + y;
}
void dd(int(*pf)(int x, int y)) {
int a = 3;
int b = 5;
int ret=pf(a, b);
printf("%d", ret);
return;
}
int main()
{
dd(add);
return 0;
}
这里是用了函数指针,这个函数指针的作用主要是用来明确指向那一个函数,好进行调用的操作,这个有个好处就是*的进行函数传参,然后调用哪个函数(获得了函数的选择权,更加方便)
就好比如你去弄个计算机,你要选择功能就可以用函数指针来进行指定的加减乘除,这样就可以不用一一例举出来,然后来判断,直接选取
五.有趣的代码,常量指针与指针常量
代码一:(*(void(* )( ))0)( )
这个(void(* )( ))这个意思就是把0强制转换为一个函数指针,然后利用解引用获取该函数的地址来进行操作
代码二:void(*aaa(int,void(* )(int)))(int);
aaa是一个函数名,先于后面的()结合,如果是指针则为(*aaa)
拆一下:部分一:void(* )( int ) 部分二:aaa(int,void(*)(int))里面的第二个元素为指向一个为int为函数参数的函数指针,外围也是,指向了一个为函数参数为int的函数指针
指针常量与指针常量:
const int*p这个是指向的值无法改变,const修饰的是int*
int* const p这个是指向的地址无法改变,const修饰的是p,指针常量
记得数组名就是一个指针常量,因为地址无法被修改
总结
我们学习了指针的各种类型,有指针与数组结合有指针与函数结合,还有各个传参的方式,这些都是需要去掌握的