指针的进阶

本文详细介绍了C语言中的指针概念,包括指针变量、指针类型、指针运算,以及如何使用指针处理数组和字符串。同时,讲解了函数指针的用法,包括函数指针数组和回调函数的概念。此外,还探讨了数组传参的不同方式,以及排序算法的应用,如冒泡排序和qsort函数。
摘要由CSDN通过智能技术生成
    • 指针的概念

  • 指针就是个变量,用来存放地址,地址唯一标识一块内存空间

  • 指针的大小是固定的4/8个字节(32位平台/64位平台)

  • 指针有类型,指针的类型决定了指针+-整数的步长、指针解引用操作时候的权限

  • 指针的运算

    • 字符指针char*

使用方法:

int main()
{
    char ch='w';
    char*p=&ch;
    *p='w';
    const char*pstr="hello bit";
    //把字符串的第一个字符的地址放到pstr指针变量中,*pstr得到的是a
    //常量字符串不能被修改,所以在前面加上const,则指针不能修改所指向字符串的内容
    printf("%s\n",pstr);
    //%s为打印字符串,从起始位置开始打印,一直到'\0'结束
}
    • 练习:

#include<stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
//创建两个数组,将字符串的内容拷贝进两个数组中,所以str1和str2是两个不同的空间,只是内容相同
    const char* str3 = "hello bit.";
    const char* str4 = "hello bit.";
//相同的常量字符串只在内存中保存一份,str3和str4都是指针,指向统一地址,所以相等
    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;
}
    • 指针数组

——存放指针(地址)的数组

如:

char* arr[4]={"abcdef","qwer","hello bit","hehe"};
//存放字符指针的数组(数组的每个元素为字符串首字母的地址)
int arr1[5]={1,2,3,4,5};
int arr2[5]={2,3,4,5,6};
int arr3[5]={3,4,5,6,7};
int arr4[5]={0,0,0,0,0};
int* arr[4]={arr1,arr2,arr3,arr4};
//整型指针数组
char *arr[4];//一级字符指针的数组
char **arr[5];//二级字符指针的数组
    • 数组指针

什么是数组指针?

字符指针——存放字符地址的指针——指向字符的指针 char*

整型指针——存放整型地址的指针——指向整型的指针 int*

浮点型的指针——指向浮点型的指针 float* double*

那么

数组指针——存放数组地址的指针——指向数组的指针

int arr[10];
//pa就是一个数组指针
int (*pa)[10]=&arr;
//p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//由于[]的优先级要高于*号,所以必须加上()来保证p先与*结合
    • &数组名VS数组名

数组名——数组首元素的地址

&数组名——数组的地址

数组首元素的地址和数组的地址从值的角度来看是一样的,但意义是不一样的

int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%p\n",arr);//类型为int*
printf("%p\n",arr+1);//跳过4个字节

printf("%p\n",&arr[0]);//类型为int*
printf("%p\n",&arr[0]+1);//跳过4个字节

printf("%p\n",&arr);//类型为int(*)[10]
printf("%p\n",&arr+1);//跳过40个字节
//&arr取出的是数组的地址,只有数组的地址才需要数组来接收
    • 数组指针的用法

看代码:

#include<stdio.h>
void Print(int(*p)[4], int r, int c)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < r; i++)
    {
        for (j = 0; j < c; j++)
        {
            printf("%d ", (*(p+i))[j]);
            //p+i表示的是第i行数组的地址,进行解引用操作得到第i行数组,
            //[j]表示第i行数组的第j个元素,由于*的优先级低于[],所以需要加()
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,0,1,2} };
    Print(arr, 3, 4);
//数组名表示首元素的地址,对于二维数组来说,二维数组是存放一维数组的数组
//所以首元素是第一行数组,传参时传的是第一行数组的地址,需要用数组指针来接收
}

回顾并指出以下代码的意思:

int arr[5];
//整型数组,数组有5个元素,每个元素是int类型
int *parr1[10];
//指针数组,数组有10个元素,每个元素是int*类型
int (*parr2)[10];
//数组指针,该指针指向一个数组,数组是10个元素,每个元素是int类型
int (*parr3[10])[5];
//数组指针数组,parr3是数组,数组有10个元素,每个元素是类型为int*[5]的数组指针
    • 数组传参、指针传参

1>一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
//数组传参,用数组接收
void test(int arr[10])//ok
{}
//数组传参,用数组接收
void test(int *arr)//ok
{}
//数组的每个元素为int类型,所以首元素的地址为int*类型
void test2(int *arr[20])//ok
{}
//数组传参,用数组接收
void test2(int **arr)//ok
{}
//数组的每个元素为int*类型,所以首元素的地址为二级指针,用二级指针接收
int main()
{
int arr[10] = {0};
//arr是一个整形数组,每个元素的类型为int
int *arr2[20] = {0};
//arr2是一个指针数组,每个元素的类型为int*
test(arr);
//整型数组传参,数组名表示首元素的地址
test2(arr2);
//指针数组传参,数组名表示首元素的地址
}

2>二维数组传参

void test(int arr[3][5])//ok
{}
//二维数组传参,二维数组接收
void test(int arr[][])//err
{}
//二维数组行可以省略,但列不能省略
void test(int arr[][5])//ok
{}
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//err
{}
//第一行数组的地址,应该用数组指针接收,整型指针无法接收
void test(int* arr[5])//err
{}
//指针数组无法接收数组的地址
void test(int (*arr)[5])//ok
{}
//数组指针接收,可行
void test(int **arr)//err
{}
二级指针用于接收一级指针的地址,无法接收数组指针
int main()
{
    int arr[3][5] = {0};
    test(arr);
    //二维数组传参,数组名表示第一行数组的地址
}

3>一级指针传参

一级指针传参,用一级指针接收

若一个函数的参数部分为一级指针时,函数能接收什么参数
void test(int*p)
{}

int a = 0;
int *p=&a;
int arr[10];

test(arr);
test(&a);
test(p);

4>二级指针传参

当函数的参数为二级指针的时候,可以接收什么参数
void test(int **p)
{}

int **ptr;
int *pp;
int *arr[10];

test(ptr);
test(&pp);
test(arr);
    • 函数指针

数组指针——指向数组的指针

函数指针——指向函数的指针

int Add(int x,int y)
{ 
    return x+y;
}
int main()
{
    //int arr[10];
    //int(*pa)[10]=&arr;
    //pa是数组指针
    int(* pf)(int,int)=&Add;
    //int(*pf)(int,int)=Add;
    //&函数名和函数名都是函数的地址,没有区别
    //pf是一个存放函数地址的指针变量——函数指针
    int ret=pf(2,3);
    //也可以写成
    //int ret=(*pf)(2,3);
    printf("%d\n",ret);
    //5
}

关于两段有趣的代码:

( *(void (*)())0 )();
//该代码是一次函数调用,调用0地址处的一个函数
//首先代码中将0强制类型转换为类型为void(*)()的函数指针
//然后去调用0地址处的函

void(*signal(int,void(*)(int)))(int);
//该代码是一次函数的声明
//声明的函数名字叫signal
//signal函数的参数有2个,第一个是int类型,第二个是函数指针类型
//该函数指针能够指向的那个函数的参数是int,返回类型是void
//signal函数的返回类型是一个函数指针
//该函数指针能够指向的那个函数的参数是int,返回类型是void
//此种写法过于难懂,可以改成
typedef void(*pf_t)(int);
//对指针类型重命名的时候需要将指针名字放在*旁边
pf_t signal(int,pf_t);

10. 函数指针数组

指针数组

//存放的是字符指针

char* arr1[10];

//存放整型指针

int* arr2[5];

函数指针数组——存放函数指针的数组

int(*pfA[5])(const char*)={&my_strlen};

//[]的优先级高于*,pfA先跟[]结合,表示数组,该数组有5个元素,每个元素是类型为

int(*)(const char*)的函数指针

当多个函数的返回值和参数都一样时,可以将它们都放到一个函数指针数组中,方便调用

函数指针数组的用途:转移表(通过输入的下标,找到数组中的某个地址,通过该地址来调用函数)

举例:写一个计算器,实现加、减、乘、除

#include <stdio.h>
//写一个计算器,实现加、减、乘、除
//初级版
void menu()
{
    printf("************************\n");
    printf("******1.Add  2.Sub******\n");
    printf("******3.Mul  4.Div******\n");
    printf("******0.Exit      ******\n");
    printf("************************\n");
}
int Add(int x,int y)
{
    return x + y;
}

int Sub(int x, int y)
{
    return x - y;
}

int Mul(int x, int y)
{
    return x * y;
}

int Div(int x, int y)
{
    return x / y;
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    do
    {
        menu();
        printf("请输入你要执行的操作:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("请输入你要进行操作的两个数字:>");
            scanf("%d %d", &x, &y);
            ret=Add(x, y);
            printf("%d\n", ret);
            break;
        case 2:
            printf("请输入你要进行操作的两个数字:>");
            scanf("%d %d", &x, &y);
            ret = Sub(x, y);
            printf("%d\n", ret);
            break;
        case 3:
            printf("请输入你要进行操作的两个数字:>");
            scanf("%d %d", &x, &y);
            ret = Mul(x, y);
            printf("%d\n", ret);
            break;
        case 4:
            printf("请输入你要进行操作的两个数字:>");
            scanf("%d %d", &x, &y);
            ret = Div(x, y);
            printf("%d\n", ret);
            break;
        case 0:
            printf("退出计算器\n");
            break;
        default:
            printf("输入不合法,请重新输入");
            break;
        }
    } while (input);
}

改造后

#include <stdio.h>
//改造版
void menu()
{
    printf("************************\n");
    printf("******1.Add  2.Sub******\n");
    printf("******3.Mul  4.Div******\n");
    printf("******0.Exit      ******\n");
    printf("************************\n");
}
int Add(int x, int y)
{
    return x + y;
}

int Sub(int x, int y)
{
    return x - y;
}

int Mul(int x, int y)
{
    return x * y;
}

int Div(int x, int y)
{
    return x / y;
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };
    do
    {
        menu();
        printf("请输入你要执行的操作:>");
        scanf("%d", &input);
        if (input == 0)
        {
            printf("退出计算器\n");
            break;
        }
        else if (input > 0 && input < 5)
        {
            printf("输入你要执行的操作数:>");
            scanf("%d %d", &x, &y);
            printf("%d\n", p[input](x, y));
        }
        else
        {
            printf("输入不合法,请重新输入\n");
        }
    } while (input);
}

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

//数组指针
int arr[10];
int (*pA)[10]=&arr;
//函数指针数组
int(*pf[5])(int,int);
//ppf是指向函数指针数组的指针
int(*(*ppf)[5])(int,int)=&pf;
//ppf先与*结合,表示ppf是一个指针,指针指向一个数组,
//该数组有5个元素,每个元素是类型为int(*)(int,int)的函数指针
//ppf+1则会跳过整个pf数组

12. 回调函数

概念:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个

函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数

的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。

举例:计算器改造版

#include <stdio.h>
//写一个计算器,实现加、减、乘、除
void menu()
{
    printf("************************\n");
    printf("******1.Add  2.Sub******\n");
    printf("******3.Mul  4.Div******\n");
    printf("******0.Exit      ******\n");
    printf("************************\n");
}
int Add(int x,int y)
{
    return x + y;
}

int Sub(int x, int y)
{
    return x - y;
}

int Mul(int x, int y)
{
    return x * y;
}

int Div(int x, int y)
{
    return x / y;
}
void calc(int (*pf)(int,int))
{
    int x = 0;
    int y = 0;
    printf("请输入你要执行的操作数:>");
    scanf("%d %d", &x, &y);
    int ret = pf(x, y);
    printf("%d\n", ret);
}
int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请输入你要执行的操作:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            calc(Add);
            break;
        case 2:
            calc(Sub);
            break;
        case 3:
            calc(Mul);
            break;
        case 4:
            calc(Div);
            break;
        case 0:
            printf("退出计算器");
            break;
        default:
            printf("输入错误,请重新输入\n");
            break;
        }
    } while (input);
}

13. 排序

冒泡排序:

#include<stdio.h>
//冒泡排序
void bubble_sort(int arr[], int sz)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

当前冒泡排序有局限:只能排整型数组的顺序

而在库函数中有一个函数可用于排序:qsort

#include<stdlib.h>
void qsort(void*base,//待排序数组的起始位置
           size_t num,//数组的元素个数
           size_t width,//一个元素是几个字节
           int(*cmp)(const void*e1, const void*e2));
           //两个元素的比较函数,e1是要比较的第一个元素的地址,e2是要比较的第二个元素的地址
           //若e1小于e2,返回一个小于0的数
           //若e1大于e2,返回一个大于0的数
           //若e1等于e2,返回0
           //void*是一种无具体类型的指针,可接收任意类型的地址,但是不能直接用,需要强制类型转换

用qsort实现整型数组排序:

#include<stdio.h>
#include<stdlib.h>
//qsort函数实现排序
int com_int(void* e1, void* e2)
{
    return *(int*)e1 - *(int*)e2;
}
int main()
{
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    //bubble_sort(arr, sz);
    qsort(arr, sz, sizeof(arr[10]), com_int);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

用qsort实现结构体的排序:

#include<stdio.h>
#include<stdlib.h>
struct Stu
{
    char name;
    int age;
};
int com_by_age(const void* e1,const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
    struct Stu s[3] = { {"wanghedi",23},{"wangsulong",33},{"xuzhisheng",26} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), com_by_age);
}

qsort函数的模拟实现:

改造冒泡排序函数,使得这个函数可以排序任意指定的数组

整型数组:

#include<stdio.h>
//qsort的模拟实现
//改造冒泡排序函数,使其可以排序任意指定的数组(整型数组)
void Swap(char* buf1, char* buf2, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
int com_int(const void* e1, const void* e2)
{
    return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, size_t sz, size_t width, int(*com)(const void* e1, const void* e2))
{
    size_t i = 0;
    size_t j = 0;
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (com((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
            }
        }
    }
}

int main()
{
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz, sizeof(arr[0]), com_int);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

结构体:

#include<stdio.h>
//qsort的模拟实现
//改造冒泡排序函数,使其可以排序任意指定的数组(结构体)
struct Stu
{
    char name;
    int age;
};
void Swap(char* buf1, char* buf2, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
int com_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void bubble_sort(void* base, size_t sz, size_t width, int(*com)(const void* e1, const void* e2))
{
    size_t i = 0;
    size_t j = 0;
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (com((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}

int main()
{
    struct Stu s[3] = { {"zhangsan",23},{"lisi",50},{"wangwu",33} };
    int sz = sizeof(s) / sizeof(s[0]);
    bubble_sort(s, sz, sizeof(s[0]), com_by_age);
}

14.关于指针和数组

//一维数组

//整型数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
//计算整个数组的大小,4*4=16字节
printf("%d\n",sizeof(a+0));
//a+0其实是数组第一个元素的地址,是地址就是4/8字节
printf("%d\n",sizeof(*a));
//a是数组首元素的地址,*a是数组首元素,计算的是数组首元素的大小,是4字节
printf("%d\n",sizeof(a+1));
//a+1是数组第二个元素的地址,是地址就是4/8字节
printf("%d\n",sizeof(a[1]));
//a[1]是数组第二个元素,计算的是第二个元素的大小,为4字节
printf("%d\n",sizeof(&a));
//&a取出的是整个数组的地址,是地址就是4/8字节
//&a--->类型:int(*)[4]
printf("%d\n",sizeof(*&a));
//&a是整个数组的地址,*&a就是拿到了数组,*和&可以相互抵消
//所以*&a就是a,a就是数组名,sizeof(*&a)就是sizeof(a)
//当*&a没有放在sizeof内部时,则为数组名,表示首元素地址
printf("%d\n",sizeof(&a+1));
//&a是整个数组的地址,&a+1跳过整个数组,指向数组后边的空间,是一个地址,大小就是4/8字节
//&a+1的类型:int(*)[4]
printf("%d\n",sizeof(&a[0]));
//&a[0]是首元素的地址,计算的是首元素地址,大小为4/8字节
printf("%d\n",sizeof(&a[0]+1));
//&a[0]+1是第二个元素的地址,地址的大小就是4/8字节
sizeof是用于计算大小的,返回类型是size_t,即为unsigned int

//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
//arr单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小,大小为6字节
printf("%d\n", sizeof(arr+0));
//arr+0表示数组第一个元素的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(*arr));
//arr表示数组首元素地址,*arr表示首元素,大小为1字节
printf("%d\n", sizeof(arr[1]));
//arr[1]表示数组第二个元素,大小为1字节
printf("%d\n", sizeof(&arr));
//&arr表示取出的是整个数组的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(&arr+1));
//&arr表示取出的是整个数组的地址,&arr+1跳过整个数组,指向数组后边的空间,是一个地址,大小就是4/8字节
printf("%d\n", sizeof(&arr[0]+1));
//&arr[0]表示首元素的地址,&arr+1跳过一个元素,指向数组第二个元素的地址,大小为4/8字节

//size_t strlen(const char*str)
printf("%d\n", strlen(arr));
//strlen用于计算字符串长度,直到'\0'结束,由于数组中不含'\0',所以为随机值
printf("%d\n", strlen(arr+0));
//随机值
printf("%d\n", strlen(*arr));
//*arr表示的是首元素,strlen(*arr)->strlen('a')->strlen(97)
//即访问地址为97的空间,非法访问,报错
printf("%d\n", strlen(arr[1]));
//和上面的代码类似,非法访问,报错
printf("%d\n", strlen(&arr));
//&arr虽然是数组的地址,但也是从数组的起始位置开始的,计算的还是随机值
printf("%d\n", strlen(&arr+1));
//&arr是数组的地址,&arr+1是跳过整个数组的地址,求字符串长度也是随机值
printf("%d\n", strlen(&arr[0]+1));
//&arr[0]是数组首元素的地址,&arr+1是数组第二个元素的地址,从第二个元素开始求字符串长度,为随机值

char arr[] = "abcdef";
//[a,b,c,d,e,f,\0]  数组是7个元素
printf("%d\n", sizeof(arr));
//sizeof(arr)表示计算整个数组的大小,为7字节
printf("%d\n", sizeof(arr+0));
//arr+0表示首元素的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(*arr));
//*arr表示首元素,大小为1字节
printf("%d\n", sizeof(arr[1]));
//arr[1]表示数组第二个元素,大小为1字节
printf("%d\n", sizeof(&arr));
//&arr表示整个数组的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(&arr+1));
//&arr表示整个数组的地址,&arr+1表示跳过整个数组,指向后面的空间,是地址大小就为4/8字节
printf("%d\n", sizeof(&arr[0]+1));
//&arr[0]表示首元素的地址,&arr[0]+1表示数组第二个元素的地址,是地址大小就为4/8字节

printf("%d\n", strlen(arr));
//arr表示数组首元素地址,从首元素开始计算字符串长度,到'\0'结束,为6字节
printf("%d\n", strlen(arr+0));
//arr+0表示首元素地址,从首元素开始计算字符串长度,到'\0'结束,为6字节
printf("%d\n", strlen(*arr));
//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问,报错
printf("%d\n", strlen(arr[1]));
//arr[1]是数组第二个元素,是98,造成非法访问,报错
printf("%d\n", strlen(&arr));
//&arr表示整个数组的地址,也是从首元素开始到'\0'结束,为6字节
printf("%d\n", strlen(&arr+1));
//&arr表示整个数组的地址,&arr+1表示跳过整个数组,统计字符串长度是随机值
printf("%d\n", strlen(&arr[0]+1));
//&arr[0]表示数组首元素,&arr[0]+1表示数组第二个元素,则从第二个元素开始,为5字节

char *p = "abcdef";
//p是一个char类型的指针,指向a的地址
printf("%d\n", sizeof(p));
//p是一个指针,大小为4/8字节
printf("%d\n", sizeof(p+1));
//p指向a的地址,p+1为b的地址,是地址大小就为4/8
printf("%d\n", sizeof(*p));
//*p是'a',sizeof(*p)计算的是字符的大小,为1字节
printf("%d\n", sizeof(p[0]));
//p[0]-->*(p+0)-->*p,同上,为1字节
printf("%d\n", sizeof(&p));
//&p表示p的地址,为二级指针,是指针大小就为4/8字节
printf("%d\n", sizeof(&p+1));
//&p表示p的地址,&p+1表示跳过p变量后的地址,是地址大小就为4/8
printf("%d\n", sizeof(&p[0]+1));
//p[0]-->*(p+0)-->*p-->'a',&p[0]表示a的地址,&p[0]+1表示b的地址,是地址大小就为4/8

printf("%d\n", strlen(p));
//从'a'开始求字符串长度,为6字节
printf("%d\n", strlen(p+1));
//p+1是b的地址,求字符串长度是5字节
printf("%d\n", strlen(*p));
//*p就是'a',即为97,非法访问,报错
printf("%d\n", strlen(p[0]));
//同上,报错
printf("%d\n", strlen(&p));
//&p得到的是p这个指针变量的起始地址,从这里开始求字符串长度完全是随机值
printf("%d\n", strlen(&p+1));
//&p+1是跳过p变量的地址,从这里开始求字符串长度也是随机值
printf("%d\n", strlen(&p[0]+1));
//&p[0]+1是b的地址,从'b'开始求字符串长度,为5字节

//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
//a单独在sizeof内部,表示整个数组,计算的是整个数组的大小,3*4*4=48字节
printf("%d\n",sizeof(a[0][0]));
//a[0][0]表示数组第一行第一列的元素,为4字节
printf("%d\n",sizeof(a[0]));
//a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的就是数组(第一行)的大小,为16字节
printf("%d\n",sizeof(a[0]+1));
//a[0]是第一行的数组名,没有单独放在sizeof内部,没有取地址,表示的就是第一行首元素的地址,即为a[0][0]
//a[0]+1表示第一行第二个元素的地址,是地址大小就为4/8字节
printf("%d\n",sizeof(*(a[0]+1)));
//*(a[0]+1)表示第一行第二个元素,计算的是元素的大小为4字节
printf("%d\n",sizeof(a+1));
//a是二维数组的数组名,数组名表示首元素的地址,就是第一行的地址,a+1就是第二行的地址,是地址大小就为4/8字节
//a+1的类型为int(*)[4]
printf("%d\n",sizeof(*(a+1)));
//a+1是第二行的地址,*(a+1)表示的是第二行,*(a+1)->a[1],第二行的大小为4*4=16字节
printf("%d\n",sizeof(&a[0]+1));
//&a[0]表示第一行数组的地址,+1跳过整个第一行数组,表示第二行数组的地址,是地址大小就为4/8字节
printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1表示第二行的地址,*(&a[0]+1)得到的是第二行,计算第二行的大小,为4*4=16字节
printf("%d\n",sizeof(*a));
//a表示首元素的地址,也就是第一行的地址,*a就是第一行,计算的是第一行的大小,为4*4=16字节
printf("%d\n",sizeof(a[3]));
//如果数组存在第四行,那么a[3]表示第四行的数组名,单独放在sizeof内部,计算第四行的大小为4*4=16字节

//sizeof不会真实地去访问空间,通过类型得出大小,放在sizeof中的表达式不会参与运算
int main()
{
    short s=3;
    int a=10;
    printf("%d\n",sizeof(s=a+2));
    //2
    printf("%d\n",s);
    //3 (sizeof内的表达式不会参与运算)
}

//C语言中,表达式有2个属性:
//2+3
//值属性:5
//类型属性:int

总结:

1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节

  1. &数组名,数组名表示整个数组,取出的是整个数组的地址

  1. 除此之外,所有的数组名都是数组首元素的地址

  1. sizeof只关注占用内存空间的大小,单位是字节,不关心内存中存放的是什么

  1. sizeof是操作符

  1. strlen是求字符串长度的,统计的是\0之前出现的字符个数,一定要找到\0才算结束,所以可能存在越界访问的情况

  1. strlen是库函数

15.练习

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,5
//a是数组名,表示首元素的地址,+1表示第二个元素的地址,*(a+1)表示第二个元素,为2
//&a表示整个数组的地址,类型为int(*)[5],+1跳过整个数组指向后面的空间
//ptr为整型指针,指向a后面的空间,-1则向后读取4个字节的内容,为5
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;
}
//程序的结果是 
0x00100014
0x00100001
0x00100004
//指针+1加的是指针所指向对象的大小
//p为结构体指针,+1加的是一个结构体的大小,为20字节,
//p被强制类型转换为unsigned int类型,+1就按照整数+1的法则
//p被强制类型转换为unsigned int*类型,+1跳过一个整形指针的大小,为4字节
//小端 x86环境
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
2000000
//&a表示整个数组的地址,&a+1跳过整个数组,指向后面的空间
//ptr1[-1]->*(ptr1-1)->向后读取一个整形(4个字节)的内容,即为4
//a是数组名,表示数组首元素的地址,强制类型转换为int类型,+1按照整数+1法则运算,再强转为int*类型
//则相当于向后移动1个字节,ptr2为整型指针,*ptr2为向后读取4个字节的内容
//数组a的内容在内存中按照小端存放,则为01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
//*ptr2访问4个字节则为00 00 00 02,由于内存中为小端存放,所以按照原顺序为02 00 00 00
//%x为打印16进制数
#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    //逗号表达式,表达式结果为最后一个表达式的值,所以a数组存储的内容为
    //1 3
    //5 0
    //0 0
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0;
}
//程序的结果为 1
//a[0]为数组名,表示数组首元素的地址,p[0]->*(p+0),即为第一个元素,为1
int main()
{
    int a[5][5];
    int(*p)[4];
    //p为数组指针,指向一个整形数组,该数组有4个元素,p+1跳过4个字节
    p = a;
    //a为数组名,表示数组第一行的地址,类型为int(*)[5]
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    //地址-地址得到两个地址直接的元素个数,如果是高地址-低地址结果就为正,如果是低地址-高地址结果为负
    //%p是打印地址(以16进制打印)
    return 0;
}
//程序结果为
ff ff ff fc   -4
int main()
{
    int aa[2][5] = {10,9,8,7,6,5,4,3,2,1};
    //10 9 8 7 6 
    //5 4 3 2 1
    int *ptr1 = (int *)(&aa + 1);
    //&aa表示整个数组的地址,+1跳过整个数组指向后面的空间,强转为int*类型,并赋值给指针ptr1
    int *ptr2 = (int *)(*(aa + 1));
    //aa是数组名,表示第一行数组的地址,*(aa+1)->aa[1]表示第二行数组首元素的地址,赋值给ptr2
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}
//程序的结果为 1,6
//ptr1指向aa数组后面的空间,正好是刚越界的部分,-1则向前挪动一个整形指向10的地址,*ptr1得到1
//ptr2指向第二行首元素,-1指向第一行最后一个元素的地址,*(ptr2-1)得到第一行最后一个元素的内容为6
#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
//数组a存放的是字符串首字符的地址
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}
//程序的结果为 at
//a为数组名,表示首元素地址,赋值给pa,pa++指向第二个元素的地址
//*pa对第二个元素解引用为at
int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);//**(cpp-2)+3
 printf("%s\n", cpp[-1][-1]+1);//*(*(cpp-1)-1)+1
 return 0;
}
//程序的结果为 
//POINT
//ER
//ST
//EW

weixin073智慧旅游平台开发微信小程序+ssm后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
python017基于Python贫困生资助管理系统带vue前后端分离毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值