0


c语言指针进阶总结

字符指针

#include <stdio.h>
int main() {
    const char* p = "abmddf";
    printf("%s\n", p);//p不可以修改常量字符串

    
    char arr[] = "absnfodak";//把常量字符串放在一个可以修改的变量中
    char* pa = arr;
    *pa = 'v';
    printf("%s\n", pa);
    return 0;
}

这里特别容易让人以为是把字符串 abmddf 放到字符指针 p 里了,但其实是把字符串 abmddf 首字符的地址放到了p中。

#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 the same\n");
    else
 printf("str1 and str2 are not the same\n");
       
    if(str3 ==str4)
 printf("str3 and str4 are the same\n");
    else
 printf("str3 and str4 are not the same\n");
       
    return 0;
}

输出:str1 and str2 are not the same

       str3 and str4 are the same

解析:用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,而C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。

数组指针

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

#include<stdio.h>
int main() {
    const char* arr[5] = { "abcdefg","shunyizhen","xuanbing" ,"cuihua","fawaikuangtuzhangsan"};
    int i = 0;
    for (i = 0; i < 5; i++) {
        printf("%s\n", arr[i]);
    }
    return 0;
}

数组指针

数组指针是指向一个数组的指针。

对比下面代码,哪种是数组指针?

int *p1[10];

int (*p2)[10];

上面的内容告诉我们,第一种是指针数组的声明,那么第二种的写法,就是数组指针的声明了。

指针数组的声明:p先和结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和*结合。

数组指针的初始化

#include<stdio.h>
int main() {
    int arr[] = { 1,2,4,7,8 };
    int(*p)[5] = &arr;//数组指针的初始化:取出数组arr的地址存放在p中
    //int(*)[5]:数组指针类型
    return 0;
}

数组名与&数组名的区别

#include<stdio.h>
int main() {
    int arr[] = { 1,2,3,4,5,6,7,8,9,10,11 };
    printf("%p\n", arr);
    printf("%p\n", arr+1);

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

在指针初阶的时候讲到,指针类型决定两件事,一是指针访问的权限,二是指针加减整数的步长。在上面的截图可以看到,数组名arr是首元素的地址,类型其实是int,那么+1就会跳过一个整型,即4个字节,而&arr,虽然也是显示首元素的地址,但其类型是int()[11],那么+1就会跳过整个数组的大小,即11个整型,44个字节。

** 数组指针到底该怎么使用?**

数组指针使用不当反而会增加不必要的麻烦。如下面这种情况,明明print1函数中只用一个一级指针便能解决的问题,使用了一个数组指针,反而更加麻烦了。

#include<stdio.h>
void print1(int* p, int sz) {
    int i = 0;
    for (i = 0; i < sz; i++)
        printf("%d ", *(p + i));//或:p[i]
    printf("\n");
}

void print2(int(*p)[11], int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) 
        printf("%d ", *((*p) + i));//或:(*p)[i]
//那是因为p存的是一行的地址,并不是首元素的地址,并不能像print1里面那样,通过直接
//+i来得到后续元素的地址,得来个*解引用
    printf("\n");    
}

int main() {
    int arr[] = { 1,2,3,4,5,6,7,8,9,10,11 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    print1(arr, sz);
    print2(&arr, sz);
    return 0;
}

数组指针在一维数组中的使用较少,更常见地是在二维数组的使用。

对于一个二维数组而言,它的数组名意味着什么?

二维数组名也是数组的首元素的地址,而二维数组的首元素就是它的第一行。换言之,二维数组名就是二维数组第一行的地址,那传参的时候,形参就得是一个数组指针类型。例题如下:

void print3(int arr[3][5], int row, int column) {
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++) {
        for (j = 0; j < column; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

void print4(int(*p)[5], int row, int column) {
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++) {
        for (j = 0; j < column; j++) {     //p+i相当于第i行的地址
            printf("%d ", *(*(p + i) + j));//*(p+i)相当于p[i],每一行首元素的地址
                                           //*(*(p + i) + j)相当于p[i][j]
        }
        printf("\n");
    }
}

int main() {
    int arr[3][5] = { {1,2,3,4,5},{4,6,8,12,16},{5,7,8,1,7} };
    print3(arr, 3, 5);
    print4(arr, 3, 5);
    return 0;
}

问:如何理解下面这段代码??

int (*parr3[10])[5];

由于[10]先与parr3结合,那么parr3[10]就是一个数组。把数组摘干净,剩下的就是它的元素类型:

int (*)[5]

可见,是一个数组指针。

parr3是一个拥有10个元素的数组,每个元素都是一个指向含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? ✔ ,数组指针,接受一行5个的地址
{}
void test(int **arr)//ok?  × ,接受一个指针的地址
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);//传arr首行的地址
}

反过来思考,当函数形参是一级指针的时候,实参应该是怎么样的?

int test(int* p){

}

int a=0;

test(&a);

int *p=&a;

test(p);

int arr[2]={0};

test(arr);

而当形参是二级指针时,情况又是如何呢?

int test(int ** p){

}

int a=8;

int *p=&a;

int** ptr =p;

int *arr[5];

test(&p);

test(ptr);

test(arr);

函数指针

函数指针顾名思义就是存放函数地址的指针。

#include<stdio.h>
int minus(int x, int y) {
    return x - y;
}
int main(){
    int x = 90;
    int y = 9;
    int z=minus(x,y);
    printf("%p\n", minus);
    printf("%p\n", &minus);
}

输出:

00C21438
00C21438

可见,不管是minus或&minus的地址都是一样的。

函数指针的定义

int main(){
    int (*Minus)(int x, int y) = minus;//函数指针的定义
    //int ret = minus(9, 2);
    //int ret = (*Minus)(9, 2);   
    int ret = Minus(9, 2);//甚至(*Minus)中的那颗*也可以拿掉
    printf("%d\n", ret);
}
int test(const char* str, double d)
{

}

int main()
{
    int (*pt)(const char*, double) = &test;
    //int (*pt)(const char* str, double d) = &test;

    return 0;
}

下面两段代码是什么意思?

代码1

((void ()())0)();

//代码2

void (signal(int , void()(int)))(int);

函数指针数组

对比指针数组

int *arr[10] ;//一个数组,存储的是指针

int (*ptr)(int ,int );// 函数指针

int (*ptr[5])(int int );// 函数指针数组,即一个数组,存储了5个这样类型(int ( * )(int int ))的函数指针(函数参数相同,返回类型相同)

函数指针数组的使用案例

//函数指针数组实现加、减、乘和除
#include<stdio.h>
void menu() {
    printf("************************************\n");
    printf("******* 1.add       2.minus ********\n");
    printf("******* 3.multiple  4.divide********\n");
    printf("******* 0.exit              ********\n");
}

int minus(int x, int y) {
    return x - y;
}

int add(int x, int y) {
    return x + y;
}

int multiple(int x, int y) {
    return x * y;
}

int divide(int x, int y) {
    return x / y;
}

int main(){
    menu();
    int x = 0;
    int y = 0;
    int input = 0;
    int (*ptr[])(int, int) = { 0,add,minus,multiple,divide };
    
    do {
        printf("请输入数字:>");
        scanf("%d", &input);
        if (input == 0) {
            printf("退出程序\n");
            break;
        }
        if (input <= 4 && input >= 1) {
            printf("请输入两个数字:>");
        
            scanf("%d%d", &x, &y);
            int ret = ptr[input](x, y);
            printf("%d\n", ret);
        }
        else {
            printf("请重新输入\n");
        }
    } while (input);
    return 0;
}

指向函数指针数组的指针

int (*ptr)(int , int );//函数指针

int (*ptr[10])(int , int);//函数指针数组

int (*(*ptr)[10])(int int ); //指向函数指针数组的指针

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数的使用

案例1. (修改上面的代码):

void calcu(int (*p)(int,int)) {
    int x = 0;
    int y = 0;
    printf("请输入两个数字:>");
    scanf("%d%d", &x, &y);
    int ret = p(x, y);
    printf("%d\n", ret);

}

int main() {
    int input = 0;
    
    do {
        menu();
        printf("请输入数字:>");
        scanf("%d", &input);
        switch (input) {
        case 1:
            calcu(add);//add在这里是回调函数
            break;
        case 2:
            calcu(minus);//minus是回调函数
            break;
        case 3:
            calcu(multiple);//multiple是回调函数
            break;
        case 4:
            calcu(divide);//divide是回调函数
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("输入有误\n");
            break;
        }
    } while (input);

    return 0;
}

案例2.qsort函数

在介绍qsort函数之前,先介绍void *这种指针类型。

void这种指针非常宽容,可以接受任意类型的地址。但如果要访问,或是递增、递减的话,那会出问题。原因在于,访问权限未知,及步长未知。因此在使用前,需将void指针,强制类型转换成相应类型。

#include<stdio.h>
int main() {
    int a = 9;
    void* p = &a;
    //如果直接 *p=10 会报错
    //或是 p++; 也会报错
    *(int*)p = 10;
    printf("%d\n", a);
    return 0;
}

qsort函数的原型:

void qsort (void* base, size_t num, size_t size,
           int (*cmp)(const void*,const void*));

void* base //待排序数据的起始地址

size_t num //待排序数据的元素个数

size_t size //待排序数据元素的大小(单位是字节)

int (cmp)(const void e1,const void* e2) //比较2个元素大小的函数指针 ,返回类型为整型,当返回值大于0时,e1指向的值排在e2的后面;当返回值小于0时,e1指向的值排在e2的前面;而当返回值为0时,e1与e2指向的值相等

qsort函数是c语言标准库提供的排序函数,任意类型的数据都可以进行排序。要知道,不同类型数据的排序方法是不一样的:整数排序,比较两个数的大小;两个字符串排序,可利用strcmp函数;而结构体也需要指定相应的排序方式。所以cmp函数就是那个根据不同类型数据,来制定的相应排序方式。

//qsort函数用于结构体的排序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct stu {
    char name[10];
    int age;
};

int cmp_by_name(const void* e1, const void* e2) {
    return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}

//结构体按照年龄排序
int cmp_by_age(const void* e1, const void* e2) {
    return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}

int main() {
    struct stu S[] = { {"zhangsan",56} ,{"lisi",28},{"ruhua",34},{"cuihua",29} };
    int sz = sizeof(S) / sizeof(S[0]);
    qsort(S, sz, sizeof(S[0]), cmp_by_name);
    //结构体的打印
    struct stu* p = S;
    for (p = S; p < &S[sz]; p++) {
        printf("%s %d\n", p->name, p->age);
    }
    return 0;
}
标签: c语言

本文转载自: https://blog.csdn.net/qq_41233305/article/details/126966607
版权归原作者 是Christy的博客呀 所有, 如有侵权,请联系我们删除。

“c语言指针进阶总结”的评论:

还没有评论