IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【c语言】-指针进阶(上) -> 正文阅读

[C++知识库]【c语言】-指针进阶(上)


前言

指针是c语言的精华,我们之前已经学习了c语言的初阶指针,对初阶指针有了一个大概的了解:

1.指针就是一个变量,是用来存放地址的,一个地址唯一标识一块内存空间,可以通过这个地址来访问这个对应内存空间的数据
2.指针的大小是固定的4/8个字节(32位平台下是4个字节,64位平台下是8个字节。同时我们平时通常看到的是x86,其实就是32位平台,只是以前的叫法,被沿用至今。x64就是64位平台,可千万不能以为x86就是86位平台)
3.指针的类型决定了指针+1/-1时指针往前跳几个字节,即指针的步长,以及指针解引用时的权限
4.指针的运算

接下来就是我对指针进阶的学习。这里对指针的研究的广度和深度将会更进一步。


1.字符指针

字符指针的一般使用方法是

#include<stdio.h>
int main()
{
	char ch = 'a';
	char* pc = &ch;
	return 0;
}

而还有一种使用方式是:

int main()
{
	const char* pch = "hello world";
	printf("%s", ch);
	return 0;
}

这时可能第一次见到的同学会比较诧异,为什么可以把字符串赋给一个字符指针呢?
其实这样使用在语法上是支持的,它的意思其实是把一个常量字符串的首字符’h’的地址存放到指针变量pch里面。 它的打印结果是就是:

在这里插入图片描述

这里的是一个常量字符串,那么一般在声明这样的一个字符串指针时,我们要在指针变量前面加上一个const修饰,表示指向的是一个常量。

从这个知识点可以往后衍伸。
C会把常量字符存储到一个单独的内存空间,当几个指针。指向同一个字符串的时候,他们实际上会指向同一块内存空间。但是相同的字符串去初始化不同的数组时,就会开辟出不同的内存空间。这句话理解可能会比较抽象,我们通过一段代码来解释一下:

#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.指针数组

指针数组我们之前初阶的时候就已经介绍过了,这里再重新提一嘴。 指针数组其实就是一个数组。一个什么数组呢?一个存放指针的数组。

它的声明方式:

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3.数组指针

3.1数组指针的定义

数组指针是数组还是指针?我们从名字就可以看出来。
草莓味饼干是草莓还是饼干?答案是饼干。
那么数组指针就是指针。一个什么指针?一个指向数组的指针

声明方式:
我们先看一段代码:

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

p1是指针数组。p2是数组指针。 为什么呢?
解释:*的优先级比[]更高,我们看p1,p1先和[]结合,那么它是一个数组。再与’*‘结合,说明它是一个存放指针变量的数组。
p2先和*结合,说明p2是一个指针,再与[]结合,说明它是一个指向数组的指针变量。

3.2数组名vs&数组名

我们来看一段代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}

运行结果如下:
在这里插入图片描述
打印结果一样,那么我们是不是就可以认为数组名等于&数组名了呢?
其实不是的。
我们再看一段代码:

#include <stdio.h>
int main()
{
 int arr[10] = { 0 };
 printf("arr = %p\n", arr);
 printf("&arr= %p\n", &arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr+1= %p\n", &arr+1);
 return 0;
 }

打印结果如下:
在这里插入图片描述

通过上面的代码我们可以发现,虽然直接打印arr的地址和&arr的地址是一样的,但是它们+1的结果截然不同,arr+1跳过了1个整型,而&arr+1跳过了10个整型,即一个数组的长度。
实际上。arr是首元素的地址,而&arr是整个数组的地址。那么这个地址的类型是什么?怎么接收呢?

本例中,&arr的类型是:int(*)[10],是一种数组指针类型。
数组的地址+1,跳过了整个数组的大小,所以&arr+1相对于&arr的差值是40.

3.3数组指针的使用

当我们想传入一个数组到函数里去的时候,这时候数组指针就起效果了。
看以下代码:
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col) {
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

void print_arr2(int (*arr)[5], int row, int col) {
    int i = 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; 
    }

我们可以看到,当我们想传入一个二维数组的时候,我们知道数组名是首元素地址,而二维数组的首元素却是一个数组,这样,我们函数在接收数组指针时就需要数组指针类型来接收。这就是数组指针的作用。

4.数组参数、指针参数

我们经常遇到这样一个问题,当我们想把数组或者指针传给函数时,函数的参数我们应该如何设计呢?

4.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
{}
void test2(int** arr)//ok
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

4.2二维数组传参

void test(int arr[3][5])//ok
{}
//void test(int arr[][])//no
//{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//no
{}
void test(int* arr[5])//no
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//no
{}
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; 
 }

5.函数指针

老惯例,我们先来看代码:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
 }

这段代码的打印结果是一致的,函数不需要区分这个。因为区分了也没有什么意义。重点我们放在后面

函数指针的变量初始化:

void (*pfun1)();

pfun1先与*结合,表示pfun1是一个指针,一个什么指针呢?一个指向返回值为void,没有参数的函数。

6.函数指针数组

函数指针数组是什么?是数组,什么数组?存放函数指针的数组,而函数指针前面已经介绍了。
函数指针数组的定义:

int (*parr1[10])();
int parr210;
int (
)() parr3[10];

这个其实是不需要去记忆的,只需要按照以下思路:

首先找到你的变量名:以parr1为例,看它左右,左边有一个*,右边有一个括号,我们知道括号的优先级比*高,所以我们可以先知道它是一个数组。是一个什么数组,它左边有一个*号,那么我们知道它是一个指针数组。同时后面跟着括号,以及前面有一个int,我们知道它是一个存放没有参数的返回类型是int的函数的指针的数组。

它的作用:转移表
仍然通过一段代码来理解:

#include <stdio.h>
int add(int a, int b) 
{
       return a + b; 
}
int sub(int a, int b) 
{
       return a - b;
}
int mul(int a, int b) 
{
       return a*b; 
}
int div(int a, int b)
{
       return a / b; 
}
int main()
{
 int x, y;
 int input = 1;
 int ret = 0;
 int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
 while (input)
 {
      printf( "*************************\n" );
      printf( " 1:add           2:sub \n" );
      printf( " 3:mul           4:div \n" );
      printf( "*************************\n" );
      printf( "请选择:" );
  scanf( "%d", &input);
      if ((input <= 4 && input >= 1))
     {
      printf( "输入操作数:" );
          scanf( "%d %d", &x, &y);
          ret = (*p[input])(x, y);
     }
      else
           printf( "输入有误\n" );
      printf( "ret = %d\n", ret);
 }
  return 0; 
  }

那么如何理解转移表的含义呢。
我们可以看到,如果我们不适用函数指针数组的话,我们的代码将是一段非常冗余繁琐的代码,其原因是swtich-case语句的不断重复,将不同情况下的函数调用情况给罗列出来。而使用了函数指针数组的话,就可以代替掉switch-case语句从而实现不同情况下将不同函数调出不同函数。
从而我们将这一功能抽象出来形成了一个名词:转移表。
由此可见,许多时候我们理解一个名词的定义时不要去死扣它的字面意思,字面意思往往非常晦涩和抽象,我们应该更多的去结合它的实际应用,把我们自身和发明这个名词的人摆在一个位置,这样我们就能更加深刻理解它发明这个名词的心理活动。

7.指向函数数组的指针

这其实就是不断的套娃,当你有一个数组或者是函数或者是什么新的类型数据的时候,它都会在内存开辟一个空间,从而会拥有一个地址,而这时你就可以发明一个新的数据类型去存储这个新的地址。这样就可以不断的套娃,你又可以发明一个存储这些新地址的数组。又可以发明一个指向这个数组的指针。所以这个指向函数数组的指针我就不过多赘述的。

其本质就是:

它是一个指针。指向的是一个数组。数组里存放的函数的地址。

往往我们从后往前读就能得到它的本质。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 20:38:17  更:2022-09-24 20:38:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:05:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码