C语言指针就应该这么学 - 指针的进阶篇

Posted 跳动的bit

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言指针就应该这么学 - 指针的进阶篇相关的知识,希望对你有一定的参考价值。

前言

💨 在C语言初识篇我们大概的对指针有了一些简单认识和概念。在正式对指针进阶的学习之前,先简单回忆下:

  • 指针就是一个变量,用来存放地址,地址唯一标识一块内存空间
  • 指针的大小是固定的4/8个字节(32位平台/64位平台)
  • 指针是有类型的,指针的类型决定了指针加减整数的步长;指针解引用操作的时候的权限
  • 指针的运算

一、字符指针

1、什么是字符指针

💨 顾名思义就是指向字符的指针

int main()
{
  char ch = ‘q’;
  char* pc = &ch;
  return 0;
}

💨 当然也可以指向字符串

int main()
{
  //这里的字符串占了10个字节的空间,指针大小最大也就8个字节,怎么可能存的下。其实这里存储的是字符串的首元素的地址
  char* pc = “hello bit”;
  //不防一验:(对pc进行解引用,并输出。如果输出’h’则证实指针存储的是字符串的首元素地址)
  printf("%c\\n", *pc);
  //相对于数组,字符串会被全部存储于内存中
  char arr[] = “hello bit”
  //当采用指针变量和数组首地址来输出字符串时,两者很相似
  printf("%s\\n", ps);
  printf("%s\\n", arr);
  return 0;
}

💨 常量字符串
在这里插入图片描述

2、 字符指针实例1:(出自《剑指offer》)

#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份及以上的必要)
因为str3和str4同时指向同一块空间,所以它们的地址是相同的
在这里插入图片描述

二、指针数组

1、什么是指针数组

💨 整型数组存放整型,字符数组存放字符。顾名思义指针数组就是一个存放指针的数组

int* arr[3];
arr数组里有3个元素,每个元素是int*类型的

2、怎么用

💨 这里介绍两种写法

#include<stdio.h>
//这种写法比较少见,没有什么应用场景:
int main01()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[3] = {&a, &b, &c};
	int i = 0;
	for(i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}
//常见的写法:
int main()
{
	int a[5] = {1, 2, 3, 4, 5};
	int b[5] = {2, 3, 4, 5, 6};
	int c[5] = {3, 4, 5, 6, 7};
	int* arr[3] = {a, b, c};
	//通过arr把a、b、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("%d ", *(arr[i][j]));//同上
		}	
		printf("\\n");
	}
}

三、数组指针

1、什么是数组指针

整型指针 -> 是指向整型的指针
字符指针 -> 是指向字符的指针
数组指针 -> 是指向数组的指针

int main()
{
	int a = 10;
	int* pa = &a;
	//----------------分割线----------------
	char ch = 'w';
	char* pc = &ch;
	//----------------分割线----------------
	int arr[10] = {1,2,3,4,5};
	int (*parr)[10] = &arr;
	//*parr是数组指针,指向10个整型元素的数组;parr的类型:把parr去掉int(*)[10]就是parr的类型
	return 0;
}

2、小例1:

💨 定义一个指针数组double* d[5] -> d是一个数组,有5个double*类型的元素,现将数组d存储

double*  (*pd) [5] = &d;
pd是一个数组指针,指向一个5个double *类型元素的数组

3、数组名

💨 我们都知道数组名有2种特殊用法:其1,sizeof(数组名)表示求整个数组的大小;其2,&数组名表示取整个数组的地址

int arr[10] = {0};
int* p1 = arr;
int (*p2)[10] = &arr;

#include<stdio.h>
int main()
{
	int arr[10] = {0};
	int* p1 = arr; //把数组首元素地址存储于p1,p1的类型是int*
	int (*p2)[10] = &arr; //这里的p2就是指向数组的指针,这个数组有10个元素,每个元素的类型是int;也可以说指针p2的类型是int[10]
	
	//p1和p2是相同的指向同一位置
	printf("%p\\n", p1);
	printf("%p\\n", p2);
	//但是p1和p2的类型不一样,它们的意义也不一样
	printf("%p\\n", p1 + 1);//跳过一个整型
	printf("%p\\n", p2 + 2);//跳过一个数组
	return 0;
}

4、数组指针的使用

💨 一般不会在一维数组上使用

#include<stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int (*parr)[10] = &arr;
	int i = 0;
	for(i = 0; i < 10; i++)
	{
		printf("%d ", *((*parr) + i));//*parr相当于arr,
	}
	return 0;
}

💨 通常是在二维数组上使用

#include<stdio.h>
void print1(int arr[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for(i = 0; i < r; i++)
	{
		for( j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\\n");
	}
}
void print2(int(*p)[5], int r, int c)//二维数组传首地址同arr[0],这里使用数组指针来接收
{
	int i = 0;
	int j = 0;
	for(i = 0; i < r; i++)
	{
		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}};
	print1(arr, 3, 5);//通常方法
	printf("------------分割线------------\\n");
	print2(arr, 3, 5);//数组指针
	return 0;
}

5、简单梳理

👁‍🗨 相信看到这里,可能许多小伙伴都会懵圈了

int arr[5]; -> arr是一个整型数组,可存储5个int类型的元素
int* parr1[10]; -> parr1是一个指针数组,可存储10个int*类型的元素
int (*parr2) [10]; -> prr2是一个数组指针,指向一个10个int类型的数组
int (*parr3[10]) [5]; -> parr3是一个数组,可存储10个int( * )[5] —— (数组指针)类型的元素,且每个数组指针指向一个5个int类型的数组

四、数组传参和指针传参

❓❔ 写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

1、一维数组传参

#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,arr2是指针数组,这里使用指针数组来接收可以匹配
{}
void test2(int **arr)//ok,arr2传的是首元素地址 ———— int*,这里使用二级指针来接收一级指针没有问题
{}
int main()
{
	int arr[10] = {0};
	int* arr2[20] = {0};
	test(arr);
	test2(arr2);
	return 0;
}

2、二维数组的传参

#include <stdio.h>
void test(int arr[3][5])//ok
{}
void test(int arr[][])//err,二维数组传参,参数可以写成数组,但列不能省略
{}
void test(int arr[][5])//ok
{}
void test(int *arr)//err,二维数组传参,传过来的是数组首地址 ———— 一维数组,不能匹配
{}
void test(int* arr[5])//err,这里是存放指针的数组不能匹配
{}
void test(int (*arr)[5])//ok,这里使用数组指针,它刚好指向二维数组的首地址
{}
void test(int **arr)//err,二级指针不能匹配一维数组的地址
{}
int main()
{
	 int arr[3][5] = {0};
	 test(arr);
}

3、一级指针传参

#include<stdio.h>
void print(int* ptr, int sz)//一级指针变量传参用一级指针接收
{
	int i = 0;
	for(i = 0; i < sz; i++)
	{
		printf("%d ", *(ptr + i));
	}
}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int* p = arr;
	int sz = sizeof(arr)/sizeof(arr[0]);
	print(p, sz);	
	return 0;
}

❓❔ 思考:函数参数部分为一级指针的时候,函数能接收什么参数?
当参数部分为一级指针的时候,函数能接收变量的地址或者一级指针变量

#include<stdio.h>
void test(char* p)
{}
int main()
{
	char ch = 'w';
	char* p1 = &ch;
	test(&ch);//ok
	test(p1);//ok			
	return 0;
}

4、二级指针传参

#include<stdio.h>
void test(int** p2)//二级指针变量传参使用二级指针来接收
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	//把二级指针ppa传参
	test(ppa);
	printf("%d\\n", a);
	return 0;
}

❓❔ 思考:当函数的参数为二级指针的时候,可以接收什么参数?
当参数部分为二级指针的时候,函数可以接收一级指针变量的地址或者二级指针变量或者指针数组的数组名

#include<stdio.h>
void test(int** p2)
{

}
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	int* arr[10] = {0};
	test(&pa);
	test(ppa);	
	test(arr);
	return 0;
}

五、函数指针

1、什么是函数指针

💨 函数指针顾名思义是指向函数的指针

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//对于变量或数组的地址使用 &,当然对于一个函数的地址也可以使用 &
	printf("%p\\n", &Add);
	printf("%p\\n", Add);
	//这里有同学就有疑问了,对于函数名同数组名是一样的概念吗?
	//&数组名 != 数组名
	//&函数名 == 函数名
	//-----------------------------------------------------
	//如何存储函数的地址
	//这里pf就是一个函数指针变量,它指向2个int类型参数,返回类型是int的函数
	int (*pf)(int, int) = &Add;
	 
	return 0;
}

2、小例1:

💨 现有一个函数,void test (char* str),将test函数存储

void (*pt) (char) = &test;

3、函数指针的使用

❓❔ 指针指向函数,但是我们如何通过指针调用函数

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;
	//把指向函数的指针解引用找到函数,传参即可
	int ret = (*pf)(3, 5);
	//int ret = *pf(3, 5);//err,这里相当于是把3和5传参后的返回值再解引用
	printf("%d\\n", ret);
	return 0;
}

❓❔ 思考 :指向函数的指针必须需要解引用吗

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//int (*pf)(int, int) = &Add;
	//&Add == Ad

以上是关于C语言指针就应该这么学 - 指针的进阶篇的主要内容,如果未能解决你的问题,请参考以下文章

C语言进阶学习笔记二指针的进阶(练习篇)

C语言笔记进阶篇第一章:指针进阶

C语言笔记进阶篇第一章:指针进阶

C语言篇+ 指针进阶(上)

C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)

C语言精华 - 指针初识篇