万字详解指针的进阶( C语言从入门到入土(进阶篇))

目录

指针的进阶

1. 字符指针

2. 指针数组

3. 数组指针

3.1 数组指针的定义

3.2 &数组名VS数组名

3.3 数组指针的使用

 4. 数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

4.4 二级指针传参

5. 函数指针

6. 函数指针数组

7. 指向函数指针数组的指针

8. 回调函数


谁都不能阻挡你成为更优秀的人。   

指针的进阶

1. 字符指针

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

一般使用:

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

还有一种使用方式如下:

int main()

{
    const char* pstr = "hello world." ; // 这里是把一个字符串放到 pstr 指针变量里了吗?
    printf ( "%s\n" , pstr );
    return 0 ;
}

代码 const char* pstr = "hello world.";

特别容易让同学以为是把字符串 hello world.
放到字符指针 pstr 里了,但是/本质是把字符串 hello world. 首字符的地址放到了pstr中。

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

那就有可这样的面试题:

 #include <stdio.h>

int main ()
{
    char str1 [] = "hello world." ;
    char str2 [] = "hello world." ;
    const char * str3 = "hello world." ;
    const char * str4 = "hello world." ;
    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 ;
}

这里最终输出的是:

这里 str3 str4 指向的是一个同一个常量字符串。 C/C++ 会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。
这里的str1和str2是用相同的常量字符串去初始化不同的数组的时候就会开辟不同的内存块。所以str1和 str2不 同, str3 str4相 同。

2. 指针数组

在前面章节我们也学了指针数组,指针数组是一个存放指针的数组。
这里我们再复习一下,下面指针数组是什么意思?  
int* arr1 [ 10 ]; // 整形指针的数组  存放int*的数组
char * arr2 [ 4 ]; // 一级字符指针的数组  存放char*的数组
char ** arr3 [ 5 ]; // 二级字符指针的数组 存放char**的数组

3. 数组指针

3.1 数组指针的定义

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int * p1 [ 10 ];
int ( * p2 )[ 10 ];
//p1, p2 分别是什么? 1
p1是 指针数组,是数组,是数组存放的是int*
p2是 数组指针,是指针,是指向 数组个数为10个,数组内容类型为int的指针。
int ( * p )[ 10 ];
p 先和 * 结合,说明 p 是一个指针变量,然后指着指向的是一个大小为 10 个整型的数组。所以 p 是一个指针,指向一个数组,叫数组指针。
// 这里要注意: [] 的优先级要高于 * 号的,所以必须加上()来保证 p 先和 * 结合。

3.2 &数组名VS数组名

对于下面的数组:

int arr[10]; 

arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
#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 ,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是 数组的地址 ,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型。
数组的地址 +1 ,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是 40

3.3 数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:
#include <stdio.h>
int main ()
{
    int arr [ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 };
    int ( * p )[ 10 ] = & arr ; // 把数组 arr 的地址赋值给数组指针变量 p
    // 但是我们一般很少这样写代码
    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 ++ )
  {
        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 ++ )
  {
        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 ;
}

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

int arr [ 5 ];   //数组
int * parr1 [ 10 ];  //指针数组
int ( * parr2 )[ 10 ];  //数组指针
int ( * parr3 [ 10 ])[ 5 ];  //存放数组指针的数组

 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?  可以 传来的是一维指针数组 arr2是首元素的内存相当于指针的内
{}                                                       存是传来的参数我们用二级指针接收也是完全可以的
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 [][]) //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 );
}

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

输出的结果: 

输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test ()
{
printf ( "hehe\n" );
}
// 下面 pfun1 pfun2 哪个有能力存放 test 函数的地址?
void ( * pfun1 )();
void * pfun2 ();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

阅读两段有趣的代码:

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

代码1:是函数调用,先把0地址转换为函数指针类型,无参数,无返回值,然后解引用,再调用。

代码2: 是一个函数声明,有两个参数,一个是int,一个是函数指针,该函数指针指向的函数返回类型为void,参数为int,signal函数的返回类型为函数指针,signal函数的返回类型也是一个函数指针,该函指针指向的函数,参数是int,返回类型为void

代码2太复杂,如何简化:

typedef void ( * pfun_t )( int );
pfun_t signal ( int , pfun_t );

6. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int * arr [ 10 ];
// 数组的每个元素是 int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int ( * parr1 [ 10 ])();
int * parr2 [ 10 ]();
int ( * )() parr3 [ 10 ];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 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 ;
    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 = %d\n" , ret );
              break ;
        case 2 :
              printf ( " 输入操作数: " );
              scanf ( "%d %d" , & x , & y );
              ret = sub ( x , y );
              printf ( "ret = %d\n" , ret );
              break ;
        case 3 :
              printf ( " 输入操作数: " );
              scanf ( "%d %d" , & x , & y );
              ret = mul ( x , y );
              printf ( "ret = %d\n" , ret );
              break ;
        case 4 :
              printf ( " 输入操作数: " );
              scanf ( "%d %d" , & x , & y );
              ret = div ( x , y );
              printf ( "ret = %d\n" , ret );
              break ;
        case 0 :
                printf ( " 退出程序 \n" );
breark ;
        default :
              printf ( " 选择错误 \n" );
              break ;
      }
} while ( input );
   
    return 0 ;
}
使用函数指针数组的实现:
#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 ;
}

7. 指向函数指针数组的指针

指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针

如何定义?

void test ( const char* str )
{
printf ( "%s\n" , 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. 回调函数

回调函数就是一个通过函数指针调用函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 
首先演示一下qsort函数的使用:
#include <stdio.h>
//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 ;
}
使用回调函数,模拟实现qsort(采用冒泡的方式)。
注意:这里第一次使用 void* 的指针,讲解 void* 的作用。
#include <stdio.h>
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 < count - i - 1 ; 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 ;
}

今天的内容就到这里了哈!!!

要是认为作者有一点帮助你的话!

就来一个点赞加关注吧!!!当然订阅是更是求之不得!

赠人玫瑰,手有余香=。=!

最后的最后感谢大家的观看!!!

你们的支持是作者写作的最大动力!!!

下期见哈!!!

评论 48
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原来45

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值