C语言 指针进阶

在前面,我们知道了指针的一些基本的情况:

        指针是一个变量.我们在口头上说的指针指的是指针变量,它用来存放地址;

        指针的大小是固定的,在32位平台上是4个字节,在64位上是8个字节;

        指针有数据类型,指针的数据类型决定了指针在+-整数时的步长;

本文主要来讨论一下指针的一些进阶用法.

一,字符指针

        在指针类型中,有一种指针类型被称为字符指针(char*);

我们来看这两种情况:

char ch = 'w';    

char *pc = &ch;

*pc = 'w';

printf("%c", *pc);//w

这种情况直接打印出w;

char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?

* pstr = 'w';   

printf("%s\n", pstr); 

这种情况时什么样的呢?

        运行起来就会发现,程序崩掉了.这是因为,上面的"hello world."是存储在内存中的只读数据区的,也就是说,这个字符串常量不能被修改.通常,我们对于常量,加上const关键字来保护常量让它不能被修改.

为了加深印象,我们来看看这样的一道题:

#include <stdio.h>
//判断一下代码的结果是什么
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    char *str4 = "hello bit.";
    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不同,str3和str4相同。

在定义指针变量的时候,常常有一个误区:

int* pa, pb;

        本来的想法是要定义一个指针pa,指针pb,但是这样的定义是错误的.只有pa是指针,而pb只是普通变量.

在看看以下代码:

typedef int* pint;

#define PINT int*

...

PINT pa, pb;

pint pc, pd;

        这种情况下,pa,pc,pd为指针变量,而pb为普通变量.define预处理只是做了简单的替换,在没有其他的做法;而typedef关键字是为一个类型起新名字.这样,pc,pd都成为了指针类型.

二,指针数组

        什么是指针数组?存放指针的数组就是指针数组.举个例子,整型数组里面,存放的是一些整数,字符数组里面,存放的是字符,类比指针数组,里面存放的就是指针了.

 定义一个字符指针数组:

char* arr[] = { "abcdef", "qwer", "zhangsan" };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
	printf("%s\n", arr[i]);
}

 结果如下: 

                 

三,数组指针

        数组指针是指针,它表示指向数组的指针.我们定义int(*p)[10],要注意的是,[]的优先级高于*,所以要加().这个定义:p和*结合,表示这是一个指针,然后指向大小为10个整型的数组,也就是说,这个指针指向一个大小为10个int类型的数组

 要注意的是,它指向数组的首地址,不是数组的首元素的地址.我们来看这样的一组代码:

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

结果为:

        我们发现, 数组名和&数组名打印的地址是一样的.但是将它们加1后,arr是跳过4个字节的大小,而&arr是跳过了4*10个字节的大小.我们发现,其实&arr和arr,虽然值是一样的,但是意义不一样. 实际上&arr 表示的是数组的地址,而不是数组首元素的地址.数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

数组指针一般用在二维数组的传参较多.为了有所区别,我们使用一维数组和二维数组来进行比较.

int arr[10] = {1,2,3,4,5,6,7,8,9,0};    

int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p,但是没必要这么麻烦,也不会这样写,这里只是比较

printf("%d", **p);//*p表示拿到数组的地址,在*一下就拿到了里面的内容1;

//printf("%d", *((*p + 0) + 0));//(*)

printf("%d", *(*p + 1));//2

        定义一个大小为10的数组,在定义一个数组指针,指向大小为10的数组,然后&arr操作,拿到这个数组的地址.要注意的是:arr是数组首元素的地址, 而p是一个指针,为什么不直接int(*p)[10] = arr呢?这里的p是指向数组的地址,arr是数组首元素的地址,不是数组的地址,数组的地址是&arr.在看看二维的情况:

int arr[3][5] = { {1,2,3,4,5}, {6,7,8,9,0}, {11,12,13,14,15} };

int(*p)[5] = arr;
print(arr, 3, 5);
printf("\n");

for (int i = 0; i < 3; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        printf("%d ", *(*(p + i) + j));//p+i表示第i行的地址,在*,就拿出了这一行,在+j,就是这一样第j个元素的地址,在*,就拿出了这个元素
    }
    printf("\n");

}

        这里arr不见了&,但还是一样的,arr是数组的首元素的地址, 二维数组的首元素的地址是第一行,也是一个数组,那么,就没必要要加&,否则反而会报错.实质上,我们如果将上面的一维数组类比成二维数组,那么它的打印就是(*).

四,函数指针

        函数指针就是指向函数的指针. 定义Void (*pfun1)();表示pfun1是一个指针,指针指向一个函数,这个函数没有参数,返回值为void.我们来看下面的代码:

#include void test()

{

        printf("hehe\n");

}

int main()

{

        printf("%p\n", test);

        printf("%p\n", &test);

        return 0;

}

 输出的是两个地址,这两个地址是 test 函数的地址。实质上,这个&加不加都无所谓.

        结果均为5,而且,编译器没有任何的报错和警告,这就说明,它们是等价的.实质上,对于函数名来说,前面的&和*都会被忽略,前面加不加&或*都没区别.我们接下来看看这一个代码:

(*( void (*)())0)();//来自<<C语言陷阱与缺陷>>

         它表示:把0强制类型转换为void (*)()类型的函数指针,解引用后再去调用0地址处这个参数为无参,返回类型为void的函数.

五,函数指针数组

        把函数的地址存到一个数组中.那这个数组就叫函数指针数组.我们定义int(*p[10])()这样一个函数指针数组,它表示:[]和p结合,说明p是一个数组,加了一个*,表示它是指针数组,在加一个(),说明这是一个函数,函数的参数是无参的,同时,int表示它的返回值是int类型.

        函数指针数组有一个用途是做转移表使用.比如,我们做一个有两个操作数的计算器,代码如下,switch部分的代码能不能简化呢?是可以的,用函数指针数组就可以做到.

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

六,指向函数指针数组的指针(简单了解)

        指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针.它的定义如下:

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)[10])(const char*) = &pfunArr;

        return 0;

}

猜你喜欢

转载自blog.csdn.net/Naion/article/details/122517808