栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C语言】指针进阶知识点

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C语言】指针进阶知识点

文章目录
    • 1.字符指针
      • (1)什么是字符指针
      • (2)字符指针的两种用法
      • (3)一道真题
    • 2.指针数组
    • 3.数组指针
      • (1)数组指针定义
      • (2)数组名的含义
      • (3)单个数组指针的常用应用
      • (4)小练习
    • 4.数组指针传参设计问题
      • (1)一维数组传参
      • (2)二维数组传参
      • (3)一级指针传参
      • (4)二级指针传参
    • 5.函数指针
      • (1)什么是函数指针
      • (2)函数指针的基本使用:
      • (3)小练习
    • 6.函数指针数组
      • (1)函数指针数组是什么
      • (2)函数指针和函数指针的综合应用
    • 7.指向函数指针数组的指针
      • (1)定义:
      • (2)例子
    • 8.回调函数
      • (1)回调函数是什么
      • (2)典型例子——qsort函数

1.字符指针 (1)什么是字符指针

指针指向类型为char的指针。

(2)字符指针的两种用法

①直接指向某个字符

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

②指向常量字符串

int main()
{
    const char* pstr = "hello";//这里不是把一个字符串放到pstr指针变量里了
    printf("%sn", pstr);
    return 0; }

其实是把常量字符串的首元素h地址放到了指针变量pstr中。

(3)一道真题

有以下程序,则运行结果为?

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

答案:

分析:C/C++会把常量字符串存放在内存中的全局区,把局部变量存放在栈区。str3和str4都是常量字符串,都指向同一片区域,而str1和str2都是局部变量,指向不同的区域,所以str1和str2不同,str3和str4不同。

2.指针数组

指针数组是每个数组元素都是指针的数组。

记作:type *数组名[个数],如int *pa[10];

3.数组指针 (1)数组指针定义

数组指针定义:指向数组的指针叫做数组指针,记做:type (*指针名) [数组元素个数],例如int (*p)[10];。

定义理解

int (*p)[10];
//*先和p结合说明这是一个指针类型,然后指着的是一个大小为10的整形数组。所以p是一个指向大小为10的整形数组的指针。
//注意:由于[]的优先级高于*,所以必须加上小括号保证p先和*结合
(2)数组名的含义

数组名绝大多数时候表示数组首元素地址,是type*类型;

只有以下两种特殊情况,&数组名和sizeof(数组名)时,这里的数组名是整个数组的地址【虽然地址编号与单独数组名相同】,是type (*) [数组元素个数]类型。

体现差异主要在指针运算结果上。

(3)单个数组指针的常用应用

一般二维数组传参

void print_arr1(int arr[3][5], int row, int col) 
{
    int i = 0,j=0;
    for(i=0; i
        for(j=0; j
            printf("%d ", arr[i][j]);
       }
               printf("n");
   }
}

数组指针传参法

void print_arr2(int (*arr)[5],int row,int col)//第一个参数就相当于传进一个指向数组元素为5个的,指向类型为数组的指针
//二维数组首元素是第一行
{
    int i=0;
    for(i=0;i
        for(j=0;j
            printf("%d ",arr[i][j]);
        }
        printf("n");
    }
}
(4)小练习

解释下列代码的意义

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[5])[5];
//整形数组
//整形指针数组
//数组指针
//(*parr3[5])表明是一个指针数组,数组中每个元素类型是int (*)[5]——数组指针,parr3是每个元素是数组指针的数组。

4.数组指针传参设计问题 (1)一维数组传参
#include 
void test(int arr[])//ok,可以直接传数组名
{}
void test(int arr[10])//ok,这里的10传了也相当于没传等价于上边
{}
void test(int *arr)//ok,此时本质上是数组首元素地址,等价于第一
{}
void test2(int *arr[20])//ok,写成数组形式,跟原来的定义形式一样,20也可以省略
{}
void test2(int **arr)//ok,指针形式,原来的数组名本质上是数组首元素的地址,而元素是int*类型的,它的地址就是二级指针,所以传成int**没毛病
{}
int main()
{
 int arr[10] = {0};//非指针数组
int *arr2[20] = {0};//指针数组
 test(arr);
 test2(arr2);
}

所以,一维数组传参可以写成数组形式也可以写成指针形式,写指针形式关键是抓住数组名的本质。

(2)二维数组传参
void test(int arr[3][5])//ok,直接传成二维数组
{}
void test(int arr[][])//no,二维数组传参,函数形参的设计只能省略第一个[]的数字,因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素.
{}
void test(int arr[][5])//ok,数组形式,省略行,不省略列,类比可知多维数组只能省略第一维
{}
void test(int *arr)//no,二维数组首元素地址时int*类型的【首元素是第一行】,它的地址应为int**类型的
{}
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);
}

二维数组传参的时候,也是可以写成数组形式,也可以指针形式。写成数组,行可以省略,列不可以省略;写成指针,必须是数组指针。

关键就是类型对应,这里指针需要细分。

(3)一级指针传参

传一级指针用一级指针接收就行。

如果参数是一级指针,实参可以是一维数组中的非指针数组数组名,一级指针,一级地址例如print(&a)中的&a【类型匹配】。

(4)二级指针传参

传二级指针用二级指针接收。

如果参数是二级指针,实参可以是一级指针的地址,二级指针,指针数组的数组名,三级指针解应用等。【只要类型匹配】

5.函数指针 (1)什么是函数指针

每个函数也有自己的地址,只要定义了就占空间,这里可以与函数栈帧空间联想对比一下。

函数地址:&函数名得到的就是函数地址。不过,对于函数来讲,&函数名和函数名都是函数的地址。

函数指针:存放函数地址编号的变量就是函数指针。定义函数返回值类型 (*指针变量名)(参数类型),例如int (*pf)(int,int)=&Add;,这里边形参名字写不写无所谓。

(2)函数指针的基本使用:
#include 
int Add(int x, int y)
{
    return x + y;
}
int main()
{
    int (*pf)(int, int) = &Add;
    int ret1 = (*pf)(2, 3);
    int ret2 = pf(2, 3);
    int ret3 = Add(2, 3);
    printf("%dn",ret1);
    printf("%dn",ret2);
    printf("%dn",ret3);
    return 0;
}

其中,定义函数指针时,&Add中的&符号可以省略。

注意,如果对于函数指针解应用时必须加括号。

所以,在函数指针这,&函数名的&其实只是一个摆设,(*函数指针)中的*也只是一个摆设,所以使用函数指针调用函数的时候我们可以直接简化成为函数指针(实参)或者函数名(实参)

(3)小练习

①求下列程序的运行结果

#include 
void test()
{
 printf("hehen");
}
int main()
{
    if(test==&test)
 		printf("samen");
    else
 		printf("differentn");
 return 0; 
 }

答案:same

②分析下列代码

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

答案:

代码一:

代码二:

可以把void(*pf_t)(int)类型重定义达到简化代码二的目的

typedef void (*pf_t)(int) ;//把void (*)(int)重定义为pf_t

那么代码二可以简化为 pf_t signal(int,pf_t);

6.函数指针数组 (1)函数指针数组是什么

函数指针数组:数组每个元素为类型都为相同函数指针类型的数组。

定义方法:返回值类型 (*数组名)[个数](返回值类型),例如int(*pa[5])(int x,int y);。可以类比指针数组。

函数指针数组的用途:转移表

(2)函数指针和函数指针的综合应用

好处:降低代码冗余

例如:(计算器)

普通写法: switch语句中case每种都定义一个对应的变量接受结果

#include 
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;
        do
       {
            printf( "*************************n" );
            printf( " 1:add           2:sub n" );
            printf( " 3:mul           4:div n" );
            printf( "*************************n" );
            printf( "请选择:" );
            scanf( "%d", &input);
            switch (input)
           {
                            case 1:
                  printf( "输入操作数:" );
                  scanf( "%d %d", &x, &y);
                  ret = add(x, y);
                  printf( "ret = %dn", ret);
                  break;
            case 2:
                  printf( "输入操作数:" );
                  scanf( "%d %d", &x, &y);
                  ret = sub(x, y);
                  printf( "ret = %dn", ret);
                  break;
            case 3:
                  printf( "输入操作数:" );
                  scanf( "%d %d", &x, &y);
                  ret = mul(x, y);
                  printf( "ret = %dn", ret);
                  break;
            case 4:
                  printf( "输入操作数:" );
                  scanf( "%d %d", &x, &y);
                  ret = div(x, y);
                  printf( "ret = %dn", ret);
                  break;
            case 0:
                    printf("退出程序n");
                    breark;
            default:
                  printf( "选择错误n" );
                  break;
           }
     } while (input);
    
    return 0; 
}

高级写法——应用函数指针数组,将函数转移

int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表

ret = (*p[input])(x, y);

#include 
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 = %dn", ret);
     }
      return 0;
 }
7.指向函数指针数组的指针 (1)定义:

函数指针数组的数组名和*括起来前边加颗星。

例如:int (*(*pfArr)[5])(int,int)=&pfArr;pfArr先和*结合代表是一个指针指向的是数组,每个元素类型是int(*)(int,int).

函数指针、函数指针数组、指向函数指针数组的定义可以结合记忆,知一推二。

(2)例子
void test(const char* str) 
{
     printf("%sn", str);
}
int main()
{
     //函数指针pfun
     void (*pfun)(const char*) = test;
     //函数指针的数组pfunArr
     void (*pfunArr[5])(const char* str);
     pfunArr[0] = test;
     //指向函数指针数组pfunArr的指针ppfunArr
     void (*(*ppfunArr)[5])(const char*) = &pfunArr;
     return 0; 
}
8.回调函数 (1)回调函数是什么

回调函数就是通过函数指针调用的函数。简言之,回调函数的参数里有其他种类函数。回调函数不是由它的实现方直接调用,是在特定条件发生时的响应。

(2)典型例子——qsort函数

qsort——可以排序任意类型的数据,可以升序可以降序,关键点是函数指针cmp,cmp可以根据具体排序规则对应实现。

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));
第一个参数:指向待排序数组第一个对象的指针
第二个参数:数组的宽度【单位长度是第三个参数的值】数组元素的个数
第三个参数:每个数组元素的大小【以字节为单位】
第四个参数:指向比较两个元素的函数指针

对于函数指针cmp返回值
 返回值小于0的话,执行交换操作
 故升序效果——e1-e2
  降序效果——e2-e1

注意:返回值处的强制类型转换和解引用问题
#include 
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
  return (*( int *)p1 - *(int *) p2);//前-后=从小到大
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
   {
       printf( "%d ", arr[i]);
   }
    printf("n");
    return 0; 
}

(3)使用回调函数,模拟实现qsort(采用冒泡排序)

实现时需要注意void*这个东西,然后比较的时候需要字节——用最小的char*进行之间,交换。

#include 
int int_cmp(const void * p1, const void * p2) 
{
  return (*( int *)p1 - *(int *) p2);
}
void _swap(void *p1, void * p2, int size) 
{
    int i = 0;
    for (i = 0; i< size; i++)
   {
        char tmp = *((char *)p1 + i);
       *(( char *)p1 + i) = *((char *) p2 + i);
       *(( char *)p2 + i) = tmp;
   }
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
    int i = 0;
    int j = 0;
        for (i = 0; i< count - 1; i++)
   {
       for (j = 0; j
            if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
           {
               _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
           }
       }
   }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
   {
       printf( "%d ", arr[i]);
   }
    printf("n");
    return 0; 
    }
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1008913.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号