0


【C语言进阶】指针的进阶【上篇】

💘作者:你我皆为凡人

💘博客主页:你我皆为凡人的博客

💘名言警句:时间不会为任何人停留,而事物与人,无时不刻也在变化着。每一个人,也都在不停向前!

💘觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!

💘系列作品:

💘

💘C语言编程刷题篇

💘经典题型系列

文章目录

前言

本文讲解了指针的进阶中的字符指针,数组指针,指针数组,数组传参和指针传参等一系列的知识,图文并茂,让你可以看懂并且理解


提示:以下是本篇文章正文内容,下面案例可供参考

💫字符指针

我们在之前的学习过程中已经了解到了初阶指针的一些知识,知道了指针的概念:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。

  3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

  4. 指针的运算。

接下来我们继续探讨指针的高级主题

在指针的类型中我们知道有一种指针类型为字符指针char*

如下:

int main()
{
    char ch = 'w';
    char* pc = &ch;
    *pc = 'b';
    printf("%c\n", ch);
    return 0;
}

像上面这种是把一个字符放入到指针中,大家还见过另外一种

如下:

下面是把abcdef这个字符串的首元素a的地址赋值到了p中,而不是整个字符串都给了p,而首元素地址打印,就顺便把后面一串都打印出来了

int main()
{
    char* p = "abcdef";
    //把字符串首元素a的地址地址赋值到p中
    printf("%s\n", p);
    return 0;
}

接下来让大家看一道面试题来解解乏,顺便理解理解上面所讲:

int main()
{
    const char* p1 = "abcdef";
    const char* p2 = "abcdef";
    char arr1[] = "abcdef";
    char arr2[] = "abcdef";
    if (p1 == p2)
        printf("p1==p2\n");
    else
        printf("p1!=p2\n");
    if (arr1 == arr2)
        printf("arr1 == arr2\n");
    else
        printf("arr1 != arr2\n");
    return 0;
}

为什么会是这样的结果呢?

p1与p2都是字符指针,而且const修饰无法改变指针,而“abcdef”是常量字符串,把首元素地址a给了p1与p2,既然是常量字符串,无法修改,只能读,在内存中存放多份好像没什么用,所以就有一块儿空间,而p1与p2指向的其实是相同的空间,所以打印相等

arr1是一个数组,而初始化会开辟一块儿空间,arr2也是一个数组,初始化开辟领一块儿空间,所以arr1与arr2并不相等

💫指针数组

指针数组是数组,是用来存放指针的数组

int main()
{
    int arr[10]; //整型数组,存放整形的
    char arr1[10];//字符数组,存放字符的
    int* arr2[10];//整形指针数组,存放整形指针的
    char* arr3[10];//字符指针数组,存放字符指针的
    return 0;
}

数组的名字相当于首元素的地址,那么我们可不可以把数组放到数组里,用指针接受,这不就是指针数组?如下代码:

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

画图解释:

实际上我们发现我们好像模拟了一个二维数组,我们还可以取出来,模拟二维数组给取出一维数组的值

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 };
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        int j = 0;
        for (j = 0; j < 5; j++)
        {
            //因为我们在之前知道,*(p+i) -- p[i]
            printf("%d ", *(parr + i) + j);
            //printf("%d ", *(parr[i] + j));
            //printf("%d ", parr[i][j]);
            //所以后面的其实都等价
        }
    }
    return 0;
}

💫数组指针

💘 数组指针的定义

数组指针是一个指针还是一个数组?

答案是指针

整型指针:指向整形数据的指针

字符指针:指向字符数据的指针

数组指针:指向数组数据的指针

那么下面哪个是数组指针呢?

int *p1[10];
int (*p2)[10]; 

解释:

int *p1[10];

p1先和【】结合,优先级的应用,代表着p1是一个数组,类型是int*的,是一个指针数组

int (*p2)[10];

p2先和*结合代表是一个指针,因为()优先级高,然后存放着10个元素,每个元素的类型是int类型,代表是存放数组的数组指针

/这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

💘 &数组名VS数组名

让我们再次讨论数组名:

对于下面的数组:

int arr【10】;

这里的arr与&arr有什么区别,都分别是什么?

我们知道arr是数组名,数组名表示数组首元素的地址,那么&arr数组名到底是什么?

我们先看一段代码:

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

运行结果如下:

我们看到数组名和&数组名与数组首元素的地址竟然一模一样,难道这三个是一样的吗?

数组名通常表示的都是数组首元素的地址

但是有2个例外:

1,sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小

2,&数组名,这里的数组名表示的依然是整个数组,所以&数组名取出的是整个数组的地址

我们再来看一段代码:

int main()
{
    int arr[10] = { 0 };
    printf("%p\n", arr);
    printf("%p\n", arr+1);

    printf("%p\n", &arr[0]);
    printf("%p\n", &arr[0]+1);

    printf("%p\n", &arr);
    printf("%p\n", &arr+1);

    return 0;
}

分析结果如下:

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。

实际上:

&arr 表示的是数组的地址,而不是数组首元素的地址。

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

如下图代码:

int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int(*p)[10] = &arr;
    //  p先与*结合是个指针,然后是指向十个元素的数组,类型是int,存放的是arr数组的地址
    //整形指针是用来存放整形的地址
    //字符指针是用来存放字符的地址
    //数组指针是用来存放数组的地址
    return 0;
}

💘 数组指针的使用

那么数组指针到底是怎么使用的呢?

既然数组指针指向的数数组,那么数组指针中存放的应该是数组的地址

切记不可以与二级指针混为一谈,二级指针是存放一级指针变量的地址

int main()
{
    char* arr[5] = { 0 };
    char* (*pc)[5] = &arr;//数组指针
    char ch = 'w';
    char* p1 = &ch;  //一级指针
    char** ph = &p1;//二级指针
    return 0;
}

而一个数组指针的使用具体如下:

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

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

int main()
{
    int arr[5];
    int* parr1[10];
    int(*parr2)[10];
    int(*parr3[10])[5];
    return 0;
}

int arr[5];

arr是整形数组,有5个元素,类型是int
int* parr1[10];

parr1是整形指针数组,有10个元素,每个类型是int*
int(*parr2)[10];

parr2是整形数组指针,先与*结合是指针,然后有10个元素,每个类型是int
int(*parr3[10])[5];

parr3是存放数组指针的数组,parr3先与【】结合是数组,然后剩下的int * 【5】是类型,正是一个指针数组,所以是一个存放数组指针的数组,简称数组指针数组

💫数组传参和指针传参

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

💘 一维数组传参

代码:

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

画图解析:

💘 二维数组传参

代码:

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

画图解析:

💘 一级指针传参

代码:

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

画图解析:

思考一个问题:反着来推,如果函数的参数部分已经定死是一级指针,那么可以用什么来传参

如 void print(int* p)

{}

如果int a = 10;

print(&a);可以传过去,因为是地址,用指针接收

如果int* ptr = &a;

print(ptr);可以传过去指针传过去指针接收

如果int arr【10】

print(arr);可以传过去,arr代表数组首元素地址,指针接收

💘 二级指针传参

代码:

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

画图解析:

思考一个问题:反着来推,如果函数的参数部分已经定死是二级指针,那么可以用什么来传参

比如:test(int** p)

{}

如果int *p1;

test(&p1);可以一级指针的地址二级指针来接收

如果int**p2;

test(p2);可以二级指针传过去二级指针接收

如果int* arr【10】;

test(arr);

指针数组名是首元素地址,每个元素是int*类型,二级指针接收没问题

💞习题练习入口

看完这些不具体操作操作那么是不可以的,可以点击上方直达去练习一些有关习题,也可以随便看一看C语言的一些习题,练习练习选择题和编程题,让自己的知识得到巩固,直接点入标题就可直达,另外想获得大厂内推资格也可以去看看:

大厂内推

标签: c语言

本文转载自: https://blog.csdn.net/weixin_45659943/article/details/124579244
版权归原作者 你我皆为凡人 所有, 如有侵权,请联系我们删除。

“【C语言进阶】指针的进阶【上篇】”的评论:

还没有评论