010+limou+C语言深入知识——(2)指针的深入理解

1、字符指针

(1)字符指针的普通用法

char a = 'A';
char* pa = &a;

但是一般来说字符指针很少这么用……更多是拿来存储一个字符串

(2)字符串的两种存储以及区别

  • 现在有了两种存储数组的方法
    • ①一个是使用char类型数组存储
    • ②另外一个是使用指向char类型的指针存储数组的首元素地址,将其当成字符串的“标记”
#include 
int main()
{
    char str1[] = "hello word.";
    char str2[] = "hello word.";
    const char *str3 = "hello word.";//将字符串的首字母h的地址存储在str3里面
    const char *str4 = "hello word.";
    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

(3)存储多个字符串----字符指针数组

int main()
{
    char* arr[3] = { "abcd", "cdf", "jiuh" };
    int i = 0;
    for(i = 0; i < 3; i++)
    {
        printf("%s\n", arr[i]);
    }
    return 0;
}

2、指针数组

比较简单,直接写下code就可以

char* arr[4];//存储了4个char*指针
char** arr[10];//存储了10个char**指针

3、数组指针

(1)数组的基础概念

  • 数组名字有两种情况如下code
//复习数组的名字含义
#include 
int main()
{
    int arr[10] = { 0 };
    
    printf("%p\n", arr);//数组名字就是首元素地址
    printf("%p\n", &arr[0]);//取出了首元素地址

    printf("%p\n, &arr);//1、取出来整个数组的地址,尽管&arr和&arr[0]的值一样,但是意义不一样(类型不一样),前者是数组类型int[10],后者是整型类型int
    printf("%zd\n", sizeof(arr));//2、这里的arr依旧是整个数组
    return 0;
}

①sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
②&数组名,这里取出来的是整个数组地址,尽管它的值和数组的首元素地址相同,两者是有区别的

最大的区别就在于int arr[10]声明后,arr是首元素地址,指针类型是int*,这是个数组指针。而能存放&arr这个地址的指针类型是int(*)[10]。这点在对两种指针进行+/-整数的时候会更加明显,因为指针会根据指向的类型对地址值进行增加 (数组指针后面会讲)

(2)数组指针的定义

数组指针也是指针

int (*p)[10];//p是一个指向一维整型数组的指针,该数组内含10个int类型

注意[]和()具有相同的优先级,结合性是从左向右结合。并且优先级都比*高。

(3)数组指针的使用

//第一种使用方法
int main()
{
    int arr[10] = {1, 2, 3, 4, 5};
    int (*p)[10] = &arr;
    return 0;
}语法逻辑没毛病,可以,但是一般不会这么使用
//第二种使用方法
#include 
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};
    
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行,指向这一行的指针类型可以写成int [][5]或者int (*)[5]
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    print_arr1(arr, 3, 5);
    print_arr2(arr, 3, 5);
    return 0;
}

(4)更多数组指针的解读

int arr[5];//单纯是个整型数组

int* parr1[10];//单纯是一个指针数组,每一个指针都指向一个int

int (*parr2)[10];//数组指针,该指针指向一个包含10个int元素的数组

int (*parr3[10])[5];//指针数组,可以思考成“int(*)[5] parr3[10]”(与int arr[5]是类似的)很明显,这是一个包含10个元素的数组,每个元素都int(*)[5]这种类型的指针,这种指针指向一包含5个元素的数组

4、数组传参和指针传参

(1)一维数组传参

#include 
void test1(int arr[])//可以这么写
{
    //code
}
void test1(int arr[10])//10写与不写都行,无所谓,C会将它忽略
{
    //code
}
void test1(int *arr)//可以的,传过来的arr1的拷贝是一个int元素地址
{
    //code
}

void test2(int *arr[20])//20写与不写都行,C依旧会将它忽略
{
    //code
}
void test2(int **arr)//也可以,传过来的arr2的拷贝是一个int*元素的地址
{
    //code
}

int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};

    test(arr1);
    test2(arr2);
}

(2)二维数组传参

void test(int arr[3][5])//可以使用,不过3会被忽略
{
    //code
}
void test(int arr[][])//不可以使用,5必须留下来
{
    //code
}
void test(int arr[][5])//可以使用
{
    //code
}
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

void test(int *arr)//不行,类型不匹配,拷贝过来的参数是一个指向一维数组的指针,而这个参数仅仅是一个一维指针
{
    //code
}
void test(int* arr[5])//不行,这是一个指针数组,根本没有关系 
{
    //code
}
void test(int (*arr)[5])//可以这么写,指针类型匹配了
{
    //code
}
void test(int **arr)//不行,指针类型不匹配
{
    //code
}

int main()
{
    int arr[3][5] = {0};
    test(arr);
}
//使用二维数组指针(深刻理解)
void print(int(*p)[20], int x, int y)
{
    //二维数组的名字就是首元素的地址,其地址就是第一行数组的地址,也就是指向一维数组类型的指针,因此不能写形参为int**
    //又因为一维数组的整体地址&arr和其首元素地址&arr[0]起始位置相同,故从值来看是一样的,但是两者的指针类型完全不同
    for (int i = 0; i < x; i++)
    {
        for (int j = 0; j < y; j++)
        {
            printf("%d", *(* (p + i) + j));//得到数组名,利用i得到每一行的数组名,利用j得到某一行的每一列的元素地址
        }
    }
}
int main()
{
    int arr[10][20] = { {1, 2, 3}, {2, 3, 4} };
    print(arr, 10, 20);
    return 0;
}

(3)一级指针传参

#include 
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)二级指针传参

#include 
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)总结

指针的类型一定要匹配好,尽管类型不匹配的时候依旧可以进行传参(因为所有地址在同一个平台都是一样大小的,只是存储一个地址,理论上用什么类型的指针都可以存储),但是在后续使用指针的时候(解引用指针)就会发生错误,指针会根据指针类型访问不同大小的内存

5. 函数指针

  • 整型指针int*
  • 字符指针char*
  • 指向数组int arr[10]的数组指针int (*p)[10]
  • 指向函数function()的函数指针int (*pf)(int, int) = function(或者int (*pf)(int, int) = &function)其中函数function()是一个有两个int参数,返回值为int的函数

(1)函数指针

  • 对于函数function(),其函数指针类型为【返回值 (*指针名) (参数类型的列表)】

  • 若想使用这个函数就要进行解引用,使用【(* pf)(参数列表)】或者【pf(参数列表)】都可以。编译器在处理的时候,没有 * 也行,但是要用 * 就一定要加括号

    • 尽管这样很矛盾,但是调用函数指针时,使用*pf和pf是一样的,即:函数名==函数的地址,而(&函数名)==函数的地址
    • 若是理解pf为指针,则*pf变成函数名字,*pf()就相当于function()
    • 若是理解pf为函数名,则pf本身就是函数的名字,pf()就相当于function()
    • C允许这两种写法,认为两种都合理
//例子
char* test(int c, float* pf)
{
    //某些代码
}
int main()
{
    char* (*pt)(int, float*)pf = test;
    test(参数列表)
    return 0;
}

(2)有关函数指针的一些有趣的代码

//代码1
(*(  void (*)()  )0)();

//从最里面开始理解void(*)()是一个指向“返回值为空,参数列表为空”函数的函数指针
//然后将0的int类型强制转化为函数指针类型,于是0成了一个函数指针类型的地址
//再解引用0这个地址,得到0地址处的函数,然后使用这个函数
//代码2
void (*  signal(int , void(*)(int))  )(int);

//等价代码如下
//typedef void(*pfun_t)(int);//注意pfun_t是一个和void(*)(int)同类型名,将pfun_t放在*旁边是为了指明pfun_t是一个指针而已,这只是语法形式要求
//pfun_t signal(int, pfun_t);

//因此有一个简化代码的技巧就是使用typedef

这两段代码来自于《C陷阱与缺陷》,是本出名的C语言书籍

6. 函数指针数组

(1)使用例子

#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 = %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
}

(2)改良后

#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 }; //转移表,即函数指针数组,这里的0(或者写NULL)起到占位的作用,理论上放什么都行,只要后续处理好就行…
    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;
}
//当计算器后续需要加入更多的运算函数时,那么使用开关语句就会显得冗长,但是使用函数指针数组就不会有这个问题,这将会大大缩减代码。但是使用函数指针也具有有缺点,它只能存放同样函数签名(函数签名定义了函数的输入和输出,即:函数签名==参数+返回值)的函数

(3)转移表的概念

像类似上面使用函数指针数组的方法就叫做转移表,具有一种跳转使用函数的效果。即“函数指针数组”==“转移表”

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) = { pfun };

    pfunArr[0] = test;
    //指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[5])(const char*) = &pfunArr;
    return 0;
}
//*代表ppfunArr是指针,[]代表这个指针指向一个内含5元素的数组,而每个元素的类型都是void (*)(const char*)
//因此按照运算符的顺序来解读是比较快的    

8. 回调函数

  • 回调函数就是一个通过函数指针调用的函数。
  • 如果你把函数的指针(地址)作为参数传递给另外一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
  • 回调函数不是由该函数的实现方式直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应

(1)例子一:使用qsort

①前要:C库函数qsort能对数组进行排序

void qsort(
    void *base,//指向了待排序数组的第一个元素 
    size_t nitems,//排序的元素个数
    size_t size,//每个元素的大小,单位是字节
    int (*compar)(const void*, const void*)//指向一个函数,这个函数可以比较两个元素的大小
    );

//其底层是使用快速排序的方法来排序的,依靠compar指向的不同函数内部不同的比较,可以解决不同类型的数据快速排序

②使用qsort:对数组进行排序

#include 
#include 
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
    return ( *(int*)p1 - *(int*)p2 );//注意不能直接解引用void指针,另外如果倒过来就是逆序输出了
}
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;
}

(2)例子二:模拟实现类似qsort函数的bubble函数(底层采用冒泡函数)

#include 
int int_cmp(const void * p1, const void * p2)//其中一个比较函数,这个是整型比较,是用户决定这个函数应该如何编写
{
    return (*(int*)p1 - *(int*)p2);
    //不过注意void不能直接解引用!!!
}

void _swap(void *p1, void * p2, int size)//其中一个排序方法,这个是排序是冒泡排序,可以由开发者决定底层排序的方法,在qsort中使用的底层函数是快排
{
    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*))//相当于qsort,但是实现逻辑不仅仅是快速排序,内部的_swap函数也可能是其他的排序算法,cmp函数可能比较不同类型的数据。     注意base是void*类型,写int*会写死的,只能限定于整型
{
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++)
    {
        int flag = 1;//①优化代码,若是没有交换就说明不需要经过排序就是有序的了
        for (j = 0; j < count - i - 1; j++)
        {
            if (cmp ((char*) base + j * size, (char*)base + (j + 1) * size) > 0)//比较函数,这里改成(char*)就可以利用size适应不同的字节
            {
                flag = 0;
                _swap((char*)base + j * size, (char*)base + (j + 1) * size, size);//排序函数,这里改成(char*)就可以利用size适应不同的字节
            }
            if(flag == 1)
            {
                break;
            }
        }
    }
}

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

注意我们不是在模拟qsort函数,而是作一个类似qsort函数的“冒泡排序通用的bubble函数”

9、一些有关数组和指针的练习题

指针和数组不是同一个东西,非等价,但是二者关系密切

//①sizeof(数组名)
//②&数组名
//以上两种情况数组名都是整个数组,注意这里的数组名必须是单独放进去一个数组名!

对于这两种情况,你还需要一些深刻的理解

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16
printf("%d\n",sizeof(a+0));//4或者8,注意这里的数组名a受到+0的影响,a是首元素地址
printf("%d\n",sizeof(*a));//4,注意这里的数组名a受到*的影响,a是首元素地址,(*a)则找到了一个首元素
printf("%d\n",sizeof(a+1));//4或者8,注意这里的数组名a受到+1的影响,a是首元素地址,(a+1)是数组的第二个元素
printf("%d\n",sizeof(a[1]));//4,就是第二个元素
printf("%d\n",sizeof(&a));//这里取出来的地址是数组的整个地址,但是也是地址,因此是4或者8。不要理解为二级指针!!!
printf("%d\n",sizeof(*&a));//16,(*(&a))就是取出整个数组
printf("%d\n",sizeof(&a+1));//4或者8,&a取出整个数组地址,+1后跳过一个指针
printf("%d\n",sizeof(&a[0]));//4或者8,首元素地址,依旧是地址
printf("%d\n",sizeof(&a[0]+1));//4或者8,第二个元素的地址,依旧是地址
//------------------------------------------------------------
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6,arr是整个数组
printf("%d\n", sizeof(arr+0));//4或者8,arr受到+0的影响,是首元素地址,还是地址
printf("%d\n", sizeof(*arr));//1,这里arr受到*的影响,是数组首元素地址,*arr解引用首元素地址得到首元素
printf("%d\n", sizeof(arr[1]));//1,arr[1]是数组第二个元素
printf("%d\n", sizeof(&arr));//4或者8,&arr是取出了整个数组的地址,还是地址
printf("%d\n", sizeof(&arr+1));//4或者8,&arr取出了整个数组,+1跳过了以个数组,得到一个地址,依旧是地址
printf("%d\n", sizeof(&arr[0]+1));//4或者8,&arr[0]取出首元素的地址,+1后得到第二个元素的地址,还是地址
printf("%d\n", strlen(arr));//随机值,因为没有\0
printf("%d\n", strlen(arr+0));//随机值,因为没有\0
printf("%d\n", strlen(*arr));//传参错误,strlen传进去应该是地址,而这里传进去了一个字符(*arr),出现error,非法访问(越界访问)。而且在函数strlen中会解引用传进来的地址,因此解引用了'c',即:解引用97,在VS2022会发生“已引发异常”的警告。
printf("%d\n", strlen(arr[1]));//传参错误,strlen传进去应该是地址,而这里传进去了一个字符(arr[1]),出现error,非法访问(越界访问),在VS2022会发生“已引发异常”的警告
printf("%d\n", strlen(&arr));//随机值,因为没有\0。注意和上一条的随机值是一样的,都要访问后面的字符
printf("%d\n", strlen(&arr+1));//随机值,因为没有\0。注意和上一条的随机值是不太一样的,一定比上一条长度差6
printf("%d\n", strlen(&arr[0]+1));//随机值,因为没有\0。注意比strlen(&arr)的随机值差了个1

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7,arr就是整个数组的地址,注意'\0'也要算进去
printf("%d\n", sizeof(arr+0));//4或者8,arr受到+0的影响,是首元素地址的指针,arr+0则是首元素地址,还是地址
printf("%d\n", sizeof(*arr));//1,受到*影响,arr是首元素地址,*arr解引用首元素地址得到首元素。可以利用这一点写sizeof(arr)/sizeof(arr[0])为sizeof(arr)/sizeof(*arr)
printf("%d\n", sizeof(arr[1]));//1,arr[1]就是第二个元素
printf("%d\n", sizeof(&arr));//4或者8,&arr取出了整个数组的地址,还是地址
printf("%d\n", sizeof(&arr+1));//4或者8,&arr取出整个地址,+1跳过了一个大小为7的数组地址,但是还是地址
printf("%d\n", sizeof(&arr[0]+1));//4或者8,&arr[0]+1得到第二个元素的地址,还是地址
printf("%d\n", strlen(arr));//6,计算出字符串长度
printf("%d\n", strlen(arr+0));//6,arr受到+0的影响,arr是一个首元素地址,+0还是首元素地址,strlen的参数就是一个地址,解引用后,就从首元素一直往后面找'\0',找到就停下,因此还是6
printf("%d\n", strlen(*arr));//传参错误,strlen传进去应该是地址,而这里传进去了一个字符(*arr),出现error,非法访问(越界访问)。
printf("%d\n", strlen(arr[1]));//传参错误,strlen传进去应该是地址,而这里传进去了一个字符(*arr),出现error,非法访问(越界访问)。
printf("%d\n", strlen(&arr));//6,&arr取出整个数组的地址,虽然指针的类型是指向整个数组,但是由于形参的类型是char*,它把传过来的指针类型自动强制类型转换了(所以这里如果是vs2022,这里会出现一个类型不匹配的警告,但是不影响使用),而&arr的值和arr数组的首元素地址值相同,故在strlen函数内部,指针++的时候,依旧是按照一个又一个字符地址的顺序解引用,直到找到'\0'就停止,因此求出来还是6
/*这个头文件中strlen的声明
size_t __cdecl strlen(
    _In_z_ char const* _Str
    );
*/
printf("%d\n", strlen(&arr+1));//随机值,和上面的代码有点类似,但是由于取出整个数组的地址后,+1跳过了一个数组地址,后面没有固定的'\0'
printf("%d\n", strlen(&arr[0]+1));//6,&arr[0]得到首元素地址,+1得到第二个元素的地址,因此从第二个元素地址开始向后解引用,直到找到'\0'为止

char *p = "abcdef";
printf("%d\n", sizeof(p));//4或者8,传入了一个char*类型的地址,还是一个地址
printf("%d\n", sizeof(p+1));//4或者6,p是首元素地址,p+1就是第二个元素地址,还是地址
printf("%d\n", sizeof(*p));//1,*p得到的是一个元素
printf("%d\n", sizeof(p[0]));//1,p[0]还是第一个元素
printf("%d\n", sizeof(&p));//4或者8,&p得到指针的指针
printf("%d\n", sizeof(&p+1));//4或者8,指针的指针+1,还是地址。注意p是char*类型,指向一个char;&p是char**类型,指向一个char*。因此跳过一个p的地址
printf("%d\n", sizeof(&p[0]+1));//4或者8,&p[0]取出首元素地址,&p[0]+1得到第二个元素的地址,还是地址
printf("%d\n", strlen(p));//6,p是字符串首元素的地址,从第一个元素地址开始解引用,直到找到'\0'
printf("%d\n", strlen(p+1));//5,p+1是第二个元素的地址,从第二个元素地址开始解引用,直到找到'\0'
printf("%d\n", strlen(*p));//传参错误,解引用得到字符,越界访问
printf("%d\n", strlen(p[0]));//传参错误,解引用得到字符,越界访问
printf("%d\n", strlen(&p));//随机值,p是char*类型指针,&p是char**类型指针,被强制转化为char*类型的指针,但是没办法确定&p的地址后面是否有'\0'的地址,故随机值
printf("%d\n", strlen(&p+1));//随机值,产生的随机值和上面的随机值没有关系(因为已经&p+1已经跳过一个p了,如果p的后面就有'\0'地址,&p+1这个地址可能比'\0'的地址还靠后)
printf("%d\n", strlen(&p[0]+1));//5,&p[0]是首元素地址,+1就是下一个元素地址,从第二个元素的地址开始解引用,直到找到'\0'为止
//------------------------------------------------------------
//二维数组
//这一部分要深刻理解好一个概念:例如定义了int a[3][4]后,则a[0][1]是指一个名字为a[0]的数组的第二个元素,a[0]+1就是第一行数组的第二个元素的地址
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48,注意这里的a是整个数组!!!因此计算每个数组的大小
printf("%d\n",sizeof(a[0][0]));//4,a[0][0]就是一个数组元素
printf("%d\n",sizeof(a[0]));//16,a[0]是第一行数组的数组名字,即整个数组的地址,内含有4个元素,计算的是整个行数组的大小
printf("%d\n",sizeof(a[0]+1));//4或者8,a[0]原本是一个第一行数组的名字,受到+1影响,这里的a[0]不再是整个行数组地址,而是该行首元素地址a[0]+1得到这一行的第二个元素地址,还是地址
printf("%d\n",sizeof(*(a[0]+1)));//4,(a[0]+1)是第一行数组的第二个元素地址,对这个地址解引用得到a[0][1]这个元素,这个元素类型是int
printf("%d\n",sizeof(a+1));//4或者8,a受到+1影响,a代表二维数组的首元素地址,即指向第一行的地址,+1后得到第二行的地址,还是地址
printf("%d\n",sizeof(*(a+1)));//16,a受到+1的影响,a代表第一行的地址,a+1后得到第二行的地址,对这个地址解引用得到整个第二行,类型是int[4],计算出整个行数组的大小
printf("%d\n",sizeof(&a[0]+1));//4或者8,&a[0]是整个第一行数组的地址,+1得到整个第二行数组的地址,依旧是地址
printf("%d\n",sizeof(*(&a[0]+1)));//16,&a[0]是整个第一行地址,+1得到整个第二行地址,对这个地址解引用,得到整个第二行数组,类型为int[4],因此计算整个第二行数组大小
printf("%d\n",sizeof(*a));//16,受到*影响,a是首元素地址,解引用得到整个第一行数组,类型为int[4],故计算整个数组的大小
printf("%d\n",sizeof(a[3]));//16,a[3]是第四行数组的数组名字,故计算整个第四行数组的大小。但是注意sizeof只通过类型计算大小,不需要进行运算,并且在编译过后就直接替换了原本的整个sizeof表达式,因此这里不属于越界访问

你可能感兴趣的:(C语言学习笔记,c语言,c++,java)