指针的详解

1.字符指针

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

一般使用:

const char* pstr = "hello bit.";
int main()
{
   char ch = 'w';
   char *pc = &ch;
   *pc = 'w';
   return 0;
}

还有一种使用方式如下:

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

代码 const char* pstr = "hello bit."

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

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。那就有可这样的面试题:

#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 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不同。

2.指针数组

在《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。

这里我们再复习一下,下面指针数组是什么意思?

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

3.数组指针

数组指针是指针?还是数组?

答案是:指针。

我们已经熟悉:

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

那数组指针应该是:能够指向数组的指针。

下面代码哪个是数组指针?

int *p1[10];//指针数组        
int (*p2)[10];//数组指针
//p1, p2分别是什么?

解释:

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];//5个元素的整形数组
int *parr1[10];//是一个数组,数组有10个元素,每个元素类型都是 int *,parr1是指针数组
int (*parr2)[10];//是一个指针,该指针指向了数组,数组里面有10个元素,每个元素类型都是 int
//parr2是数组指针
int (*parr3[10])[5];//是一个数组,该数组有10个元素,每一个元素都是数组指针,指针指向的数组有5个元素
//每一个元素都是int

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? √
{}
int main()
{
 int arr[10] = {0};

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-1);
 return 0;
}

思考:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

例如:

void test1(int *p)
{}
//test1函数能接收什么参数? 传 取地址/一维指针
void test2(char* p)
{}
//test2函数能接收什么参数?

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 函数的地址。

那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

阅读两段有趣的代码:

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

1.(void(*)())为函数指针类型,()是强制转换,所以把后面的0转换成函数指针类型,0就是一个函数的地址,*解引用然后(*(void (*)())0)就是一个函数,后面加一个()就是调用的意思,调用的内容为空参,返回类型为void

2.signal为函数名,里面有两个参数(一个int类型,一个函数指针类型),(*signal(int,void(*)(int))为函数signal的申明,该函数指向函数的参数是int,返回类型是void

signal函数返回类型也是函数指针,函数指向函数的参数是int,返回类型是void

void (*signal(int , void(*)(int)))(int);    
//等于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 (*)() 类型的函数指针。

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

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>
//qsort函数的使用者得实现一个比较函数
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;
}

//*pfArr[4](int ,int)是函数指针的数组

//(*(*pffArr[4])(int,int)

*pffArr是一个数组指针,指针指向的数组有4个元素,每个元素类型都是int(*)(int,int)也就是函数指针,使用回调函数,模拟实现qsort(采用冒泡的方式)。

注意:这里第一次使用 void* 的指针,讲解 void* 的作用。

void* 类型的指针可以访问任何类型

void* 类型的指针不可以解引用,因为你解引用的时候不知道是什么类型多少字节p不知道

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

9.指针的练习

1.一维整形数组

2.字符数组

3.字符数组

4.字符数组

5.字符数组

6.指针数组

7.指针数组

8.二维数组

总结:

数组名的意义:

1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3.除此之外所有的数组名都表示首元素的地址。

10.指针笔试题

1.

int main()
{
   int a[5] = { 1, 2, 3, 4, 5 };
   int *ptr = (int *)(&a + 1);
   printf( "%d,%d", *(a + 1), *(ptr - 1));
   return 0;
}

2.

//这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int)p + 0x1);
return 0;
}

3.

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0;
}

4.

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0;
}

5.

int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

a[4][2] - 就是第五行第三个元素

int(*p)[4]-是一个指针指向4个int整形数组

所以p整个数组占a 4个整形

p[4]=*(p+4) p代表整个p数组所以向后面跳4个数组,一个数组是四个整形,所以跳到a[3]里面第二个 p[4][2]=*(*(p+4)+2) 所以在p[4]往后面跳两个就是p[4][2]的地址

&p[4][2] - &a[4][2] 就是算他两之间有几个元素 :所以是4个

但a的地址大,而且整形打印负数无影响,所以是-4,但%p打印的是地址,地址存的是16进制的补码

所以-4的补码为:1000 0000 0000 0000 0000 0000 0000 0100 -原码

1111 1111 1111 1111 1111 1111 1111 1100 -补码

所以为: 0x f f f f f f f c

所以打印结果为: 0xfffffffc ,-4

6.

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

&aa取的是整个数组,+1就是指向数组末后一位在转换为int *类型,所以*(ptr1-1)就是往前走了一位,也就是指向了10

aa+1,aa是数组名,也就是数组首地址,也就是第一行元素,+1就是指向了第二行,同理,-1就是往前走了一位,也就是指向了5,所以打印结果为 10 5

7.

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

char* a[] 意思是a这个数组存的元素都是char*指针,所以work,ar,alibaba分别被char*指针所指

char* *pa = a; a是首元素的地址,pa是指针变量,是一级指针,char**是二级指针只能指向一级指针,所以char**pa指向char*,然后pa++,所以char*++,所以指向第二个char*,char*又指向at,所以打印出来的是at

8.

int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);//POINT
 printf("%s\n", *--*++cpp+3);//ER
 printf("%s\n", *cpp[-2]+3);==*(*CPP-2)+3   ST
 printf("%s\n", cpp[-1][-1]+1);==*(*(CPP-1))-1   EW
 return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值