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

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

指针的进阶


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中。


image.png


上面代码的意思是把一个常量字符串的首字符 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 ;
}


运行结果如下:


image.png


可见数组名和&数组名打印的地址是一样的。

难道两个是一样的吗?

我们再看一段代码:

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


image.png


根据上面的代码我们发现,其实 &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 ;
}

输出的结果: 


image.png


输出的是两个地址,这两个地址是 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 ;
}


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

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

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

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

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

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

下期见哈!!!

相关文章
|
27天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
82 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
27天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
50 9
|
27天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
43 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
132 13
|
1月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
1月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
109 3
|
1月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
42 1
|
1月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
1月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。