C语言|指针

目录

#1 指针是什么

#2 两个符号

#3 指针和指针类型

#4 野指针

#4 指针运算

#5 指针和数组

#6 二级指针

 #7 字符指针

#8 指针数组

#9 数组指针

#9.1 (数组名)与(&数组名)

#9.2 打印数组指针对应的内容

#9.3 练习

#9.4 数组和指针做函数参数

#9.4.1 一维数组传参

#9.4.2 二维数组传参

#9.4.3 二级指针传参

#10 函数指针

#11 函数指针数组

#12 指向“函数指针的数组”的指针

#13 **回调函数

#14 const修饰指针

#15 一些题目


#1 指针是什么

        指针就是变量,用来存放地址的变量。 指针的大小是固定的4或8个字节,取决于是32位还是64位平台(地址总线的宽度)

#2 两个符号

        取地址符:&

        间接寻址符:*

        若x是变量,则&x表示x在内存中的地址;若p是指针,则*p表示p指向的对象;

#3 指针和指针类型

        指针类型决定了:指针解引用的权限有多大; 以及指针走一步能走多远;

#4 野指针

        指 指针的指向位置是不可知的。

        成为野指针的原因:1.指针未初始化;2.越界访问;3.指针指向的空间释放

        下列例子属于第三点的情况:

int* test()
{
    int a = 10;
    return &a;
}

int main()
{
    int* p = test(); // 出test()函数之后,a变量已经被销毁了(因为a是局部变量,但是原先a所在位置的10还在,只是它不属于a变量了)
    *p = 20;

    return 0;
}

如何规避野指针:

  1. 在使用指针前进行初始化(不知道初始化为什么时,初始化为NULL)
  2. 小心指针越界
  3. 指针指向空间释放及时置为NULL
  4. 指针使用之前检查有效性
  5. 避免返回局部变量的地址
if(p != NULL) // 使用这种方式可以减小对无效指针的赋值
    *p = 2;

#4 指针运算

        C语言只有三种指针算术运算:1:指针加整数;2:指针减整数;3:两个指针相减;

                指针加减数字:根据指针变量的类型,进行地址增长计算

        两个指针相减:得到的是两个指针元素个数

                前提:两个指针指向同一块空间(同一数组)

int my_strlen(char* str)  // 用普通方法求字符串长度
{
    int count = 0;
    while(*str != '\0')
    {
        count++;
        str++;
    }
    return count;
}

int my_strlen(char* str)  // 用指针-指针求字符串长度
{
    char* start = str;
    while(*str != '\0')
    {
        str++; 
    }
    return (str-start);
}

        指针关系运算

                只有两个指针指向同一个数组时,指针的关系运算才有意义

#define N_VALUES 5
float values[N_VALUES];
float *vp;

//指针+-整数;指针的关系运算
for(vp = &values[0]; vp < &values[N_VALUES]; )  // values[N_VALUES]不在values数组内,它是values数组后的一块内存单元
{
    *vp++ = 0; // 先*vp=0,再vp++
}
for (vp = &values[N_VALUES]; vp > &values[0];) // 先--,再赋值
{
    *--vp = 0;
}
for(vp = &values[N_VALUES-1]; vp >= &values[0]; vp--) // 先赋值,再减减
{
    *vp = 0;
}

                上面两段代码实现的功能是一样的,但是应避免使用第二段代码(两段减减的代码),因为标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。 

#5 指针和数组

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

    int* p = arr;  // 这句话也等于int* p = &arr[0];
    printf("%d\n", 2[arr]);  // 这种格式不常见
    printf("%d\n", arr[2]); 
// arr[2]<==>*(arr+2)<==>*(p+2)<==>*(2+p)<==>*(2+arr)<==>2[arr]
    printf("%d\n", p[2]);

    return 0;
}

        结果都为3

#6 二级指针

int main()
{
    int a = 10;
    int* pa = &a; // 一级指针
    int* *ppa = &pa; // 二级指针
}

        int * pa = &a; // *表示pa是指针,int表示pa指向的对象是int类型;

        int* *ppa = &pa; // *表示ppa是指针,int*表示ppa指向的对象是int*类型;

 #7 字符指针

        字符指针不仅可以指向字符,还可以指向字符串(本质上是指向字符串第一个字符的地址),如下:

const char* ps = “hello world”;  // 把字符串首字符h的地址赋值给ps;内存中,hello world后面还有个\0
printf("%s\n", *ps);  // hello world,本质上是指向字符串第一个字符的地址,
printf("%c\n", *ps);  // h
*p = 'w';  // 如果ps没有const,这句话可能会报错(因为字符串不能被更改)
int main()
{
    char strl[] = "hello bit.";  
    char str2[] = "hello bit.";
    const char* str3 = "hello bit.";  // 字符串的首字母h的地址赋给str3
    const char* str4 = "hello bit.";  // 字符串的首字母h的地址赋给str4
    if(strl == str2)  // 如果str1和str2的地址(也即首元素的地址)相等
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");
    if(str3 == str4)  // 如果str3和str4的地址相等
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");

    return 0;
}

        以上代码的运行结果为:

                str1 and str2 are not same

                str3 and str4 are same

        str3和str4存储的是同一个常量字符串(如下图)。第一个if比较的是两个字符串的地址(首元素的地址)(因为str1和str2是两块独立的内存空间),第二个if比较的是同一个h的地址(不是比较内容,要比较两个字符串的内容是否相同,要用strcmp())

#8 指针数组

        指针数组是数组,是用来存放指针的数组;数组指针是指针,是用来存放数组的指针;

        区分指数数组和数组指针:

                []的优先级比*高

                int* p1[10]; // p1 为指针数组,p1先于[]结合再与*结合

                int(*p2)[10];  // p2 为数组指针,p2先于*结合再与[]结合

        存放指针的数组,本质上是数组;

// 声明举例
int* parr[5];

int a = 10;
int b = 20;
int c = 30;


int* parr[10] = {&a, &b, &c};

int i = 0;
for(i=0; i<3; i++)
{
    printf("%d", *(parr[i]);
}

可以用指针数组模拟二维数组;

int arr1[4] = { 1, 2, 3, 4 };
int arr2[4] = { 2, 3, 4, 5 };
int arr3[4] = { 3, 4, 5, 6 };
int* parr[3] = { arr1, arr2, arr3 };

int i = 0;
for(i=0; i<3; i++)
{
    int j = 0;
    for(j=0; j<4; j++)
    {
        printf("%d", parr[i][j]);  //对于数组,arr[i]相当于*(arr+i),parr[i][j]=*(parr[i]+j)
    }
    printf("\n");
}

        详解parr[i][j]:首先解释parr[i],因为parr为一个指针数组,因此可以用parr[i]来访问parr存储的元素(比如,当i=0时,parr[0]表示arr1),这和普通的数组访问元素的方法一样;再来解释parr[i][j],由于parr[i]已经指向了arr1、arr2、arr3中的任意一个,而arr1、arr2、arr3都是数组,因此也可以用[]来访问其中的元素;

        详解*(parr[i]+j):parr[i]和上面解释的一样;再来解释parr[i]+j,parr[i]所代表的地址进行j偏移(比如,i=0,j=2,parr[0]表示arr1,arr1是arr1的地址,同时也是arr1[0]也即元素1的地址;parr[0]+j表示arr1的地址偏移2,指向元素3的位置);最后来解释*(parr[i]+j),还是以i=0,j=2为例,parr[0]+j表示arr1中元素3的地址,对其进行解引用操作后,得到元素3;

#9 数组指针

        指向数组的指针,用来存放数组的地址

        数组指针的定义举例如下:

int arr[10] = {1, 2, 3, 4, 5};
int (*parr)[10] = &arr; // parr和*先结合再与[]结合,使用它是数组指针,parr里的每个元素都是int 

double* d[5];
double* (*pd)[5] = &d; // pd是一个数组指针 ,d中的每个元素都是double指针,所以是double*

#9.1 (数组名)与(&数组名)

        就单纯打印地址而言,他们的输出是相同的,都是首元素的地址(是元素!!!!!例如一维数组arr的首元素就是arr[0],二维数组的首元素就是arr[0]其表示第一行),但是涉及到其他操作(比如对应的指针+1)时,结果就不一样了。

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

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

        例子如下:

int arr[10] = {0};

int* p1 = arr;
int (*p2)[10] = &arr;

printf("%p\n", p1);
printf("%p\n", p1+1);
printf("%p\n", p2);
printf("%p\n", p2+1);

        40=10*4,因此p2+1是跳过了整个数组。

        另一个例子:

#9.2 打印数组指针对应的内容

int main ()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int (*pa)[10] = &arr;  // 这里(*pa)后面的10必须写上
    int i=0;
    for(i=0;i<10;i++)
    {
        printf("&d "*((*pa)+i));
    }
    return 0;
}

        首先*pa取出的是arr首元素的地址,(*pa)+i表示arr[i]的地址,最后再解引用*((*pa)+i)得到arr数组中的内容。

        上面这种访问方式不常用,一般要访问数组用的是和int *p = arr; 然后再对*(p+i); 得到arr中的元素;

        数组指针一般用于二维数组的传参!见9.4.2.

      

#9.3 练习

        说出下列代码的含义:

练习题
 int arr[5];arr为数组arr为包含5个int元素的数组
int *parr1[10];parr1为数组  [ ]的优先级比*高,parr1先与[10]结合,所以parr1为数组;将parr1[10]去掉,得到int * ,因此parr1是存放整型指针(int *)的数组
int (*parr2)[10];parr2为指针     parr2先与*结合,所以parr2为指针;将*parr2去掉,得到int [10],因此parr2是存放整型数组(int [10])的指针
int (*parr3[10])[5];parr3为数组     

parr3先与[10]结合,所以parr3为数组;将parr3[10]去掉,得到int (*) [5],再拿去*,剩下int [5];

因此,parr3是一个存放10个元素的数组,其中,每个元素又都是一个指针,每个指针存储的又都是包含5个元素的数组的地址,该数组中每个元素又都是int类型(如下图)

               

               

        补充以下代码:

                char* arr[5] = {0};

                ___ pc = &arr;

                ==>  arr是一个指针数组,而pc必须是数组指针(因为数组指针是用来存放数组的地址的),因此写为(*pc)[5];再者,arr中每个元素的类型为char*类型,因此最后完整的补充是 char* (*pc)[5] = &arr;

#9.4 数组和指针做函数参数

#9.4.1 一维数组传参

void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr) // 注意,因为arr2为存放一级指针的数组,所以*arr先取得数组里的内容(指针),再*取得该指针里的内容
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};  // 指针数组
    test(arr);
    test2(arr2);
}

        以上函数定义的参数都是可以的。

#9.4.2 二维数组传参

void test(int arr[3][5], int r, int c)
{}
void test(int arr[][5], int r, int c)
{}
// 使用数组指针传参
void test(int (*arr)[5], int r, int c)  // 二维数组首元素(第一行)的地址(也即一个一维数组的首元素地址),因为第一行有五个元素,所以是[5]
{
    // int (*arr)[5]是指针数组,类型是int* [5] 
    int i = 0;
    for(i=0; i<r; i++)
    {
        int j = 0;
        for(j=0; j<c; j++)
        {
            printf("%d", *(*(arr+i)+j));  // (arr+i)表示指向arr的第i行;*(arr+i)表示拿到第一行的数组名,*(arr+i)+j表示第j列,*(*(arr+i)+j)拿到第i行第j列的元素
        }
    }
}

int main()
{
    int arr[3][5] = {0};
    test(arr, 3, 5);  // 传的是二维数组第一行的地址(也即一个一维数组的首元素地址),是一个一维数组的地址,所以函数原型里用数组指针
}

        以上的都是可以的。

        二维数组的首元素是其第一行的地址;

        二维数组传参,函数形参只能省略第一个[]的数字

#9.4.3 二级指针传参

// 举例
void test(int** p2)
{
    **p2 = 20;
}
int main()
{
    int a = 10;
    int* pa = &a;
    int** ppa = &pa;

    test(ppa);
    test(&pa); // 传以及指针变量的地址
    
    int* arr[10] = { 0 };
    test(arr); // 传一级指针的数组

    return 0;
}

        由于test函数的参数p2是一个二级指针,因此可以传入二级指针、一级指针的地址、存放一级指针的数组(因为数组名等于地址,而又因为该数组存放的是一级指针,因此属于地址里面再放地址,因此也可以)。

#10 函数指针

        指向函数的指针(存放函数地址的指针); &函数名,就可以得到函数的地址,也可以直接函数名,因为函数名就是函数的地址(如下图); 

        注意:数组名!=&数组名 但是 函数名==&函数名

int Add(int x, int y)
{
    return 0;    
}
int test(const char* str)
{
    return 0;
}


int mian(void)
{
    int (*pf)(int, int) = &Add; // 函数指针变量
    // 也可以这么写:int (*pf)(int, int) = Add; 由此推得,Add==pf
    int (*pftest)(const char*) = test;  // 注意const

    
    // 函数指针的运用
    int ret = (*pf)(3, 5); // pf前的*是没有作用的,加多少个*都没问题
    // 也可以写成两句int ret = 0; ret = (*pf)(3, 5);
    // 也可以写成int ret = pf(3, 5);
    // 也可以写成int ret = Add(3, 5); 也即最普通的函数调用
    // 不能写成 *pf(3, 5)
    printf("%d", ret);

    return 0;
}
( *( void(*)() )0 ) ();

        分析上面的代码:

                void(*)() --- 是一个函数指针类型

                (void(*)())0 --- 将0强制类型转换(0被解释为一个函数地址)

                *(void(*)())0 --- 对0地址进行解引用

                (*(void(*)())0)() --- 调用0地址处的函数

        总结:该代码用于调用0地址处的函数,该函数无参,返回类型为void。

void (*signal(int, void(*)(int)))(int);

        分析上面的代码:

                void(*)(int) --- 是一个函数指针类型,该函数指针指向一个参数为int,返回类型为void的函数

                signal(int, void(*)(int)) --- singal是一个函数,有两个参数,一个参数为int类型,另一个参数为函数指针类型;且signal函数的返回类型也是一个函数指针,该指针指向一个参数为int,返回类型是void函数。

        总结:相当于void(*)(int) signal(int, void(*)(int)); 是一个函数声明(函数定义后,把函数定义加上分号后放在main函数前面那个东西)。

         简化:

typedef void(*pfun_t)(int);

pfun_t signal(int, pfun_t);

        函数指针可以用于简化具有冗余代码(见回调函数);

#11 函数指针数组

        存放函数指针的数组。有些地方也叫转移表。

int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int main()
{
    int (*pf1)(int, int) = Add;
    int (*pf2)(int, int) = Sub;
    int (*pfArr[2])(int, int) = {Add, Sub}; // 函数指针数组
    // 或 int (*pfArr[2])(int, int) = {pf1, pf2};
}

        函数指针数组在有较多函数(返回值、函数参数相同)时,可以简化代码:

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 menu(void)
{
    printf("-------------------------");
    printf("-----1. add   2. sub-----");
    printf("-----3. mul   4. div-----");
    printf("-----      0. exit  -----");
    printf("-------------------------");
}
int main(void)
{
    int input = 0;
    do{
        menu();
        int (*pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};
        int x = 0;
        int y = 0;
        int ret = 0;
        printf("请选择:>");
        scanf("%d", &input);
        if(input >= 1 && input <= 4)
        {
            printf("%d %d", &x, &y);
            ret = (*pfArr[input])(x, y);
            printf("ret = %d\n", ret);
        }
        else if(input == 0)
        {
            printf("退出程序\n");
            break;
        }
        else
        {
            printf("输入错误\n");
        }
    }while(input);
    return 0;
}

#12 指向“函数指针的数组”的指针

int (*p)(int, int); // 函数指针
int (* p2[4])(int, int); // 函数指针的数组
int (* (*p3)[4])(int, int) = &p2; // 指向“函数指针的数组”的指针

#13 **回调函数

        回调函数:一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

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 menu(void)
{
    printf("-------------------------");
    printf("-----1. add   2. sub-----");
    printf("-----3. mul   4. div-----");
    printf("-----      0. exit  -----");
    printf("-------------------------");
}
int Calc(int (*pf)(int, int))
{
    int x = 0;
    int y = 0;
    printf("请输入2个操作数>:");
    scanf("%d %d", &x, &y);
    return pf(x, y);
}
int main(void)
{
    int input = 0;
    do{
        menu();
        int ret = 0;

        printf("请选择:>");
        scanf("%d", &input);

        switch(input)
        {
        case 1:
            ret = Calc(Add);
            printf("ret = %d\n", ret);    
            break;
        case 2:
            ret = Calc(Sub);
            printf("ret = %d\n", ret);    
            break;
        case 3:
            ret = Calc(Mul);
            printf("ret = %d\n", ret);    
            break;
        case 4:
            ret = Calc(Div);
            printf("ret = %d\n", ret);    
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("输入错误,请重新输入\n");
            break;
            
        }
    }while(input);
    return 0;
}

        用冒泡排序来举例:

int bubble_sort(int arr[], int sz)
{
    int flag = 1;  // 假设数组已经排好序
    int i = 0;
    for(i = 0; i < sz - 1; i++)  // 趟数
    {
        int j = 0;
        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;
                flag = 0;
            }
        }
        if(flag == 1)  // 比较一趟后,一次交换都没有,则跳出
        {
            break;
        }
    }
    
}

// qsort函数的声明:
//void qsort(void* base, size_t num, size_t width, int(* cmp)(const void* e1, const void* e2);
int cmp_int(const void* e1, const void* e2)
{
    if(*(int*)e1 > *(int*)e2)
        return 1;
    else if(*(int*)e1 == *(int*)e2)
        return 0;
    else
        return -1;
    // return (*(int*)e1 - *(int*)e2);
}

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++;
    }
}
// 自己写一个qsort
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void*e1, const void*e2)
{
    int flag = 1;  // 假设数组已经排好序
    int i = 0;
    for(i = 0; i < sz - 1; i++)  // 趟数
    {
        int j = 0;
        for(j = 0; j < sz-1-i ; j++)  // 
        {
            if(cmp((char*)base+j*width, (char*)base+(j+1)*width) > 0)
            {
                // 交换
                Swap((char*)base+j*width, (char*)base+(j+1)*width, width);
                flag = 0;
            }
        }
        if(flag == 1)  // 比较一趟后,一次交换都没有,则跳出
        {
            break;
        }
    }
    
}

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函数的代码
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
}

        void*的作用:

                void* 是无具体类型的指针,可以接收任意类型的地址;

                但不能解引用操作,也不能加减整数,需要根据具体类型来强制类型转换,再解引用;

#14 const修饰指针

const的作用:

  1. const 修饰变量,变量不能通过赋值被改变,但可以通过地址来改变
  2. const 修饰指针变量
    a. const 放在*号左边,如const int * p = &num;,表示p指向的对象不能通过p来改变,但是p变量本身的值可以改变(p指向别的变量,但不能通过p来改变一个内存单元的值)
    b. const 放在 *号右边,如int * const p = &num;表示p指向的对象可以通过p来改变,但是p变量本身的值不能被改变(p不能指向别的变量,但可以通过p来改变指向的内存单元的值)
    c. const也可以同时放在 *号左右边;
# include <assert.h>
char* my_strcpy(char* dest, const char* src)  // 返回类型为char*,便于其返回值被其他直接引用;返回目标空间的首地址
{
	char* ret = dest;
	
	assert(src != NULL);
	assert(dest != NULL);
	
	while(*dest++ = *src++);

	return ret;
}

#15 一些题目

int main()
{
    int a[] = { 1,2,3,4 };

    printf("%d", sizeof(a)); // 16,sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节;
    printf("%d", sizeof(a+0)); // 4或8,a不是单独放在sizeof内部,也没有取地址,所以a是首元素的地址,a+0还是首元素的地址,因为地址是4或8个字节,所以结果为4或8
    printf("%d", sizeof(*a)); // 4,a<==>&a[0],a+0<==>&a[0]+0,故*a<==>*&a[0]<==>a[0],因此*a的sizeof就是首元素的字节大小
    printf("%d", sizeof(a+1)); // 4或8,a+1还是第二个元素的地址
    printf("%d", sizeof(a[1])); // 4,第二个元素的字节大小
    printf("%d", sizeof(&a)); // 4或8,&a取出数组的地址
    printf("%d", sizeof(*&a)); // 16,一个数组的字节大小(整个数组的字节大小)
    printf("%d", sizeof(&a+1)); // 4或者8,地址,&a+1跳过了整个数组,指向了数组后面的一个内存单元,因此表示该内存单元的地址
    printf("%d", sizeof(&a[0])); // 4或8,首元素的地址
    printf("%d", sizeof(&a[0]+1)); // 4或8,第二个元素的地址
}
int main()
{
    char arr[] = { 'a','b','c','d','e','f'};

    printf("%d\n", sizeof(arr)); // 6,整个数组的字节大小
    printf("%d\n", sizeof(arr+0)); // 4或8,首元素的地址
    printf("%d\n", sizeof(*arr)); // 1,首元素的字节大小
    printf("%d\n", sizeof(arr[1])); // 1,第二个元素的字节大小
    printf("%d\n", sizeof(&arr)); // 4或8,数组的地址
    printf("%d\n", sizeof(&arr+1)); // 4或8,跳到整个数组后的一个内存单元的地址
    printf("%d\n", sizeof(&arr[0]+1)); // 4或8,第二个元素的地址

    // strlen()函数原型的参数是const char*
    printf("%d\n", strlen(arr)); // 随机值(大于等于6),strlen通过'\0'来判断结束,arr数组没有'\0'
    printf("%d\n", strlen(arr+0)); // 随机值,和上面一样
    printf("%d\n", strlen(*arr)); // 出错,*arr相当于'a',也相当于97,但地址为97的空间不属于arr,因此不能访问。(野指针问题)
    printf("%d\n", strlen(arr[1])); // 出错,和上面一样
    printf("%d\n", strlen(&arr)); // 随机值,和最上面两行一样
    printf("%d\n", strlen(&arr+1)); // 上面的随机值-6,
    printf("%d\n", strlen(&arr[0]+1)); // 上上面的随机值-1 
}
int main()
{
    char arr[] = "abcdef"; // 对比前面的一道题,这种写法会在最后加上'\0'

    printf("%d\n", sizeof(arr)); // 7,整个数组的字节大小
    printf("%d\n", sizeof(arr+0)); // 4或8,首元素地址的字节大小
    printf("%d\n", sizeof(*arr)); // 1,首元素的字节大小
    printf("%d\n", sizeof(arr[1])); // 1,第二个元素的字节大小
    printf("%d\n", sizeof(&arr)); // 4或8,数组的地址
    printf("%d\n", sizeof(&arr+1)); // 4或8,跳到整个数组后的一个内存单元的地址
    printf("%d\n", sizeof(&arr[0]+1)); // 4或8,第二个元素的地址

    printf("%d\n", strlen(arr)); // 6
    printf("%d\n", strlen(arr+0)); // 6
    printf("%d\n", strlen(*arr)); // 出错,*arr相当于'a',也相当于97,但地址为97的空间不属于arr,因此不能访问。(野指针问题)
    printf("%d\n", strlen(arr[1])); // 出错,和上面一样
    printf("%d\n", strlen(&arr)); // 6
    printf("%d\n", strlen(&arr+1)); // 随机值
    printf("%d\n", strlen(&arr[0]+1)); // 5
}
int main()
{
    char* p = "abcdef"; // 对比前面的一道题,这种写法(常量字符串)也会在最后加上'\0'

    printf("%d\n", sizeof(p)); // 4或8,指针变量的大小
    printf("%d\n", sizeof(p+1)); // 4或8,指针变量的大小
    printf("%d\n", sizeof(*p)); // 1,首元素的字节大小
    printf("%d\n", sizeof(p[0])); // 1,首元素的字节大小
    printf("%d\n", sizeof(&p)); // 4或8,变量p的地址的大小
    printf("%d\n", sizeof(&p+1)); // 4或8,变量p后一位置的地址的大小
    printf("%d\n", sizeof(&p[0]+1)); // 4或8,'b'的地址大小

    printf("%d\n", strlen(p)); // 6
    printf("%d\n", strlen(p+1)); // 5
    printf("%d\n", strlen(*p)); // 出错,*p相当于'a',也相当于97,不能访问该地址。(野指针问题)
    printf("%d\n", strlen(p[0])); // 出错,和上面一样
    printf("%d\n", strlen(&p)); // 随机值,p地址大小为4个字节,有可能在这四个字节里会有00(\0),有可能是在p地址后的若干个字节后遇到00(\0),一切未知
    printf("%d\n", strlen(&p+1)); // 随机值,&p+1跳过了p地址的四个字节,它会在经过多少个字节遇到00(\0)不知道,因此这里是随机值
    printf("%d\n", strlen(&p[0]+1)); // 5
}
int main()
{
    int a[3][4] = {0};

    printf("%d\n", sizeof(a)); // 3*4*4=48
    printf("%d\n", sizeof(a[0][0])); // 4
    printf("%d\n", sizeof(a[0])); // 4*4=16,单独的a[0]单独放在sizeof中,表示二维数组第一行
    printf("%d\n", sizeof(a[0]+1)); // 4,a[0]没有单独放在sizeof内部,也没有取地址,因此要理解成a[0][0]+1<==>a[0][1],也即第一行第二列的地址,因此为4
    printf("%d\n", sizeof(*(a[0]+1))); // 4,第一行第二列的元素的字节大小
    printf("%d\n", sizeof(a+1)); // 4或8,a虽然是二维数组的地址,但是并没有单独放在sizeof内部,也没取地址,因此a表示首元素的地址,二维数组的首元素是其第一行,因此a就是第一行的地址,a+1表示第二行的地址
    printf("%d\n", sizeof(*(a+1)); // 4*4=16,第二行元素的字节大小
    printf("%d\n", sizeof(&a[0]+1); // 4或8,第二行地址的字节大小
    printf("%d\n", sizeof(*(&a[0]+1)); // 4*4=16,第二行元素的字节大小
    printf("%d\n", sizeof(*a); // 4*4=16,a虽然是二维数组的地址,但是并没有单独放在sizeof内部,也没取地址,因此a表示首元素的地址,二维数组的首元素是其第一行,因此a就是第一行的地址,*a取出第一行的元素
    printf("%d\n", sizeof(a[3]); // 16,它不会去真的访问第四行
}
struct Test
{
    int Num; // 4Bytes
    char* pcName; // 4Bytes
    short sDate; // 2Bytes
    char cha[2]; // 一共2Bytes
    short sBa[4]; // 一共8Bytes
}* p = (struct Test*)0x100000;

// 在x86环境下,结构体Test类型的变量大小为20个字节
int main()
{
    printf("%p\n",p+0x1); // 0x100014,加1跳过整个结构体,0x100000+20=0x100014
    printf("%p\n",(unsigned long)p+0x1); // 0x100001,0x100000=1048576,+1=1048577=0x100001
    printf("%p\n",(unsigned int*)p+0x1); // 0x100004,转为指针,所以加1跳过4个字节
}
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); // 4,2 00 00 00
}

int main()
{
    int a[3][2] = {(0,1),(2,3),(4,5)}; // 不是{{0,1},{2,3},{4,5}},这种才是两行三列的二维数组,(0,1)是逗号表达式,最终的结果是1,因此int a[3][2] = {(0,1),(2,3),(4,5)}相当于int a[3][2] = {1,3,5},也即a里面存着1 3 5 0 0 0
    int* p;
    p = a[0];
    printf("%d", p[0]); // 1
    return 0;
}
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]); // FFFFFFFC,-4,用%p打印时,直接将-4的补码打印出来,也即FF FF FF FC
    
    return 0;
}

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));  // 跳过第一行,aa+1相当于aa[1],对(aa+1)解引用就得到第二行第一个元素的地址,如果还有一个解引用就是拿到第二行第一个元素
    printf("%d,%d", *(ptr1-1), *(ptr2-1)); // 10,5
    
    return 0;
}
int main()
{
    char* a[] = {"work", "at", "alibaba"}; // 把w a a(首字母)的地址放在a中,a中每个元素为char*类型
    char** pa = a;

    pa++;
    printf("%s\n", *pa); // at,因为是用%s打印

    return 0;
}
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,cpp一开始指向c+3,对其自增,此时cpp指向c+2,对其解引用的到c+2,再解引用得到POINT
    printf("%s\n", *-- * ++cpp + 3); // ER,同理++cpp后cpp指向c+1的位置,然后解引用得到c+1,再自减,得到c,然后解引用得到E的地址,再加3,得到E(ENTER的第二个E字母)的地址,最终打印出来为ER,此时cp[2]为c,而不是c+1,而cpp指向cp[2]
    printf("%s\n",*cpp[-2] + 3); // ST,cpp[-2]故cpp指向c+3,对其解引用得到F的地址,再加3得到S的地址,因此打印出来为ST,此时CPP指向cpp[2]不变
    printf("%s\n",cpp[-1][-1] + 1); // EW,cpp[-1]指向c+2,cpp[-1][-1]得到N的地址,加1得到E的地址

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值