指针学习整理(上半部分)

本文详细介绍了C语言中的字符指针、指针数组和数组指针的概念及用法。通过实例解析了字符指针的一般和特殊使用,探讨了指针数组和数组指针的存储与操作,以及数组传参和指针传参的差异。文章深入浅出,有助于理解C语言中指针的精髓。
摘要由CSDN通过智能技术生成

目录

前言:

1. 字符指针

    1.1 字符指针的一般使用

    1.2 字符指针的特殊使用

2. 指针数组

3. 数组指针


前言:

(整个涉及)

  • 1. 字符指针
  • 2. 指针数组
  • 3. 数组指针
  • 4. 数组传参和指针传参
  • 5. 函数指针
  • 6. 函数指针数组
  • 7. 指向函数指针数组的指针
  • 8. 回调函数

(本上半部分涉及)

  • 1. 字符指针
  • 2. 指针数组
  • 3. 数组指针
  • 4. 数组传参和指针传参

    指针的基本概念:

  1.  指针就是个变量(指针变量),用来存放地址,地址唯一标识一块内存空间(内存会划分为小的内存单元,每个内存单元都有一个编号,这个编号就被称为地址,把地址也叫做指针(总的来说:内存编号 = 地址 = 指针)(指针或地址,要进行存储,就可以存放到指针比变量中(例如:int* p;)))。
  2.  指针的大小是固定的4/8个字节(32位平台/64位平台)(总的来说:多少位平台就决定了不同类型的指针只能具有一个固定的大小)。
  3.  指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。(此条概念是极为重要的:这就是为什么同样大小指针变量前,必须需具备类型的意义)
  4.  指针的运算。

1. 字符指针

    1.1 字符指针的一般使用

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'a';
    return 0; 
}

        这里,就是将字符常量存储到开辟的char类型的空间ch中,然后将空间ch的第一个内存编号(地址)(char是一个字节,即是其本身的内存编号)放入为char*类型的pc中,即说明pc是指针变量,随后通过多指针变量pc的解引用(*)找到ch的空间,并将空间内容里的字符常量 ' w ' 更改为字符常量 ' a ' 。

    1.2 字符指针的特殊使用

int main()
{
    const char* p = "abcdef"; //这里是把一个字符串第一个字符的地址存入指针变量p中
    printf("%s\n", p);
    return 0; 
}

         从此运行代码,也可以证明指针变量p中存储就是字符串常量 " abcdef " 中首字符 ' a ' 的地址。而printf函数可以实现以传入地址进行向打印,然后打印到 ‘ \0 ’ 才停止,所以就可以打印出完正的字符串。(总的来说:字符串常量的存放是连续的,我们存放了第一个字符的地址,其实也就相当于存放了整个字符串常量的地址)

注意:

        此处," abcdef "是字符串常量,在C语言中有规定,常量是无法进行修改的,于是:

         这是一个错误的操作,会导致整个代码的崩溃,是一个非常危险的操作,于是为了预防此类错误,我们应该运用const进行修饰:

         这样在后续的编写中就会进行警告(并且,也是无法编译过去的)。就可以进行一种保护,以防出现更改的情况。(并且,在有些编译器下,你不编译const就会报警告)

深入理解:

        那就有一个问题了:这 " abcdef "是在哪里创建的?是内存本身就存在?还是因为我们这样写了,内存中才会出来这个字符串,并且让p指向它? 

        其实 " abcdef "是放到常量区的, 内存之中简易的来说,有栈区,堆区,静态区与常量区,而常量区是一经过创建就不能进行修改的,是只能进行读取的。

一道经典点的笔试题:

#include <stdio.h>

int main()
{
    char str1[] = "abcdef";
    char str2[] = "abcdef";
    const char *str3 = "abcdef";
    const char *str4 = "abcdef";
    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; 
}

        可以试着做一做,看看答案是什么。

         最终运行我们会发现,成功实现打印的就只有第一个if else语句的第二条与第二个if else语句的第一条。

str3 与 str4:

        我们要知道,在前面我们已经说明 " abcdf "是常量字符串,是不能被修改的,并且其还存储于静态区中,于是对于编译器来说,无法被修改的两个完全相同的字符串常量,有没有必要用两个空间进行分别存储?答案当然是没有必要的,为节省空间编译器选择的是str3与str4,同时指向储存那条字符串常量的空间,于是本质上来说,str3 == str4。

str1 与 str2:

        在str3 与 str4中是因为不能进行修改,并且完全相同,所以没有必要进行再次空间创建,然而我们要知道,str1 与 str2是用字符串 " abcdef "进行了初始化,其实可以被修改的,所以是要用不同空间进行存储(难道你能说,其由一个数据进行初始化,那后期的变化就必须一样吗?(存储到一个空间中,那他们就是同时显示那个空间里的内容))。

并且:

        str1 与 str2所指向的内容是在栈区中的,str3 与 str4所指向的内容是在静态区的。

2. 指针数组

        指针数组是一个存放指针的数组。

我们就来谈谈它所存在的意义:

        如果我们需要运用大量的指针:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* pa = &a;
	int* pb = &b;
	int* pc = &c;

	return 0;
}

        直接的书写就会使得代码显得极为的臃肿,这是极其不方便的,于是便有了指针数组:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* arr[3] = { &a,&b,&c };

	return 0;
}

(空间是假设的,为了便于讲解与理解) 

         说白了,其就是数组,只是与普通数组的区别就是,这个数组的以元素就是指针。

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* arr[3] = { &a,&b,&c };
	
	for (int i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	printf("\n");
	return 0;
}

         需要注意的是 arr[ i ] 是地址,所以我们需要进行解引用。

不同样式的指针数组:

int* arr1[10]; //整形指针的数组
char* arr2[4]; //一级字符指针的数组
char** arr3[5];//二级字符指针的数组
//.......
#include <stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[3] = { arr1,arr2,arr3 };
	
	return 0;
}

        parr作为指针数组,就是将arr1的首元素地址存储在parr[0]中,arr2的首元素地址存储在parr[1]中,arr3的首元素地址存储在parr[2]中。其实,我们就可以发现:出了一个类二维数组。

#include <stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[3] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	
	return 0;
}

便于理解(可以理解为):parr [ i ] [ j ]  =  ( * ( parr + i ) ) [ j ] =  * ( parr [ i ] + j ) 。
(C语言在运行后,数组arr[ i ]会直接被转为:*(arr + i ))

3. 数组指针

        数组指针是指针?还是数组?

        是指针!(是一个能够指向数组的指针。)

int main()
{
	int a = 10;
	int* p = &a; //整型指针 - 指向整型的指针,存放整型变量的地址

	char ch = 'a';
	char* pc = &ch; //字符指针 - 指向字符的指针,存放字符变量的地址
	
	return 0;
}

        以普通的指针为例,那么这个所谓的数组指针,就是指向数组的指针,存放数组的地址。(数组指针的概念)

        接下来,我们需要注意一个点:

#include <stdio.h>

int main()
{
	int arr[3] = { 1,2,3 };
	
	printf("%p\n", arr);
	printf("%p\n", &arr);
	printf("%p\n", &arr[0]);
	
	return 0;
}

        虽然,三者最终取出来的地址是一样的,但是,却不是完全一样的意思,其中:arr,&arr[0]是仅仅是首元素的地址而已,而&arr是整个数组的地址,只不过,就算是整个数组,但也应该从首元素开始。

        那就有一个问题了,我们知道 “ 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。” arr 与 &arr[0]是int*类型的指针,那&arr呢?于是便有了数组指针。

        于是根据arr数组,是由3个int类型的元素组成,于是便有了数组指针:元素类型 (指针)[元素的个数]

        根据操作符的属性:parr先与*结合,说明parr是指针,后*parr与[3]结合,说明*parr是一个元素有3个的数组的指针,而最前面的int,就说明指向的那个的具有3个元素的数组的每个元素的类型为int。所以parr就是指针,其指向了数组,其类型为int(*)[3]。

        当数组存储的是指针的时候:

需要注意的点:

  1. 数组指针的方括号中,只能方常量表达式。
  2. 数组指针的方括号中的数字不能乱写,这代表了数组指针+-整数的步长,指针解引用操作的时候的权限。
  3. *parr相当于数组名,数组名又是首元素的地址,所以*parr就是&arr[0]。

数组指针的二维数组的运用:

        这里就会提到为什么指针数组存储数组的地址时会很想二维数组。(二维数组的深层次理解)

        首先我们来看看二维数组,例如:int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };

         二维数组的首元素的地址就是第一行的地址(本人是利用指针的方式进行理解的)

        看不列,就看行。就将列忽略掉,这么不就是一个三个元素的指针数组?而且还是一个特别的指针数组,他是不同的数组却又是连续的,简直就是一个典型的指针数组!

        干脆那就简单除暴一点:a = {1,2,3,4,5};b = {2,3,4,5,6};c = {3,4,5,6,7}。

 

         那这样,这就是一个最最最普通的数组,那arr是首元素的地址,首元素是a,a又是{1,2,3,4,5},{1,2,3,4,5}又是一个数组,那它的地址是什么?是不是一二维数组第一行的地址?

//用于验证二维数组的数组名是否为第一行元素的地址
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{1024,3,4,5,6},{3,4,5,6,7} };
	//                             ----  为了便于观察
	
	printf("%d\n", *(*(arr + 1)));
	//*(arr + 1)跨过了5个int类型的空间
	//* (   *(arr + 1)   )对跨过了5个int类型的空间进行解引用
	return 0;
}

这就说明:

        二维数组名需要用一个函数指针来进行接收!  (降维的操作)

void print2(int (*p)[5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (j = 0; j < r; j++)
		{
			//p+i是指向第i行的
			//*(p+i)相当于拿到了第i行,也相当于第i行的数组名
			//数组名表示首元素的地址,*(p+i) 就是第i行第一个元素的地址
			printf("%d ", *(*(p + i) + 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} };
	//写一个函数,打印arr数组
	print2(arr, 3, 5);
	return 0;
}

注意:

        当使用的是&arr时:

int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
int (*ptr)[3][5] = &arr;

当数组指针与指针数组进行了结合:

         与前面一个相似,只不过是将arr1,arr2,arr3中的各5个的int类型的变量,变为int*类型的指针变量。

注意:对于指针构建的理解方法

  1. 提取指针变量部分(是一个怎么样的指针变量)
  2. 提取指针变量部分后剩余的部分(指针变量具有怎样的一个类型)

        例如:int* (*parr3[3])[5]。

  1. 提取指针变量部分:就是parr3[3],parr3与[]结合,说明是一个数组,然后方括号里面是3,说明是一个元素有三个的数组。
  2. 提取指针变量部分后剩余的部分:就是int*(*)[5],括号里的*说明parr3[3]是指针数组,而[5]说明其是一个指向数组的指针数组,而int说明被指向的指针都是int类型的元素。

4. 数组传参和指针传参

        在写代码的时候难免要把【数组】或者【指针】传给函数,于是便有了这个内容。

        (此处:需要声明一样,虽然是以数组的形式接收,但其实都是 " 骗人的 ",这只是编译器支持的写法,本质上编译器在执行的时候还是一个指针罢了,那个数组只是一个形式罢了,就是让你更加容易的理解)

    4.1 一维数组传参

        在形参中,有以数组的形式与指针的形式接收。

#include <stdio.h>

void test(int arr[]) //对
{}

void test(int arr[10]) //对
{}

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);
}

        我们主要注意的是,在用数组进行接收的时候,其实[ ]里的数字没有什么意义 ,因为我们实参在进行传送的时候,根本不会创建一个新的数组,所以,对大小是没有必要的。

例如:

        就入一个数组有10个元素,我接收的时候也就写5个,没有意义就是没有意义,就算是我写个1000,也一样。形参部分的大小是可以忽略的。

        但是,这是十分不建议的,虽然也不是错的。

        其实之所以有用数组接收的形式,是因为:C语言,为了使小白学的时候显得更加友好,毕竟以实参数组传出,再以形参数组进行接收,会十分易于理解,所以,对于如果已经已有一定水平的人来说,还是用指针接收好些。

    4.2 二维数组传参

        需要注意,对于行是没有什么意义的,但是列是不能被省略的,这也是C语言的语法规定。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

//数组
void test(int arr[3][5]) //对
{}

void test(int arr[][]) //错
{}

void test(int arr[][5]) //对
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。

//指针
void test(int *arr) //错
{}

void test(int* arr[5]) //错
{}

void test(int (*arr)[5]) //对
{}

void test(int **arr) //错
{}

int main()
{
     int arr[3][5] = {0};
     test(arr);
}

    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]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

    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;
}


         指针真的是C语言中最为奇妙的知识点,学好指针就能够很容易的更加深层次的理解。例如:这上半部分的指针数组与数组指针,我就感觉十分的奇妙,创建者们简直是一个天才。真想看看他们是怎么一步一步的创造出现在的程序语言。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川入

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值