C语言-指针的进阶


前言

本文主要讲解了c语言中指针的进阶内容,在初阶的基础上进行延伸.

内存会划分为一个个的内存单元

每个内存单元都有一个独立的编号,-编号也称为地址

地址在C语言中也被称为指针

指针(地址)需要存储起来-存储到变量中,这个变量也就被称为指针变量

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

地址是物理的电线上产生

32为机器-32根地址线 -1/0

32个0/1组成的二进制序列,把这个二进制序列作为地址,32bit位才能存储这个地址

也就是需要4个字节才能存储,所以指针变量的大小是4个字节

同理64位机器上,地址的大小是64个0/1组成的二进制序列,需要64bit位存储,也就是8个字节。所以指针变量的大小是8个字节。


一、字符指针 

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

//在指针的类型中我们知道有一种指针类型为字符指针char*
int main()
{
    char ch = 'w';
    char* pc = &ch; //pc就是字符指针
    *pc = 'a';  

    char arr[] = "abcdef"; //[a b c d e f \0]
    const char*p = "abcdef";  //常量字符串
    printf("%s\n",p);  //abcdef
    printf("%c\n",*p); //a

    return 0;
}

 经典面试题小试牛刀

int main()
{
    char str1[] = "hello xiaofan.";
    char str2[] = "hello xiaofan.";

    const char* str3 = "hello xiaofan.";
    const char* str4 = "hello xiaofan.";

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

二、数组指针

数组指针是数组还是指针 ----是指针哦

数组指针类比:

整型指针-指向整型变量的指针,存放整型变量的地址的指针变量

字符指针-指向字符变量的指针,存放字符变量的地址的指针变量

数组指针-指向数组的指针,存放的是数组的地址的指针变量

//数组指针
//数组名的理解
//数组名是数组首元素的地址
//2个例外:1.sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
//2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址
//除此之外,所有的地方的数组名都是数组首元素的地址
// int main()
// {
//     int arr[10] = {0};
//     printf("%p\n",arr); // int*
//     printf("%p\n",arr+1); 

//     printf("%p\n",&arr[0]); //int*
//     printf("%p\n",&arr[0]+1); 

//     printf("%p\n",&arr); //int(*)[10]
//     printf("%p\n",&arr+1); 

//     return 0;
// }

//对于数组名来说
//&数组名得到的是数组的地址
// int main()
// {
//     int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//     //数组的地址,存储到数组指针变量
//     //int[10] *p = &arr;   //err
//     int(*p)[10] = &arr;
// }

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

// int main()
// {
//     int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//     int (*p)[10] = &arr; //*&arr -> arr
//     int i = 0;
//     for (i = 0;i<10;i++)
//     {
        
//         printf("%d ",*((*p)+i));
//         printf("%d ",(*p)[i]);
//     }
// }

//数组指针怎么使用呢?一般在二维数组上才方便

//二维数组传参,形参是二维数组的形式
// void Printf(int arr[3][5],int r,int c)
// {
//     int i = 0;
//     for (i = 0; i<3;i++)
//     {
//         int j = 0;
//         for(j = 0;j < 5;j++)
//         {
//             printf("%d ",arr[i][j]);
//         }
//         printf("\n");
//     }
// }
// int main()
// {
//     int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
//     Printf(arr,3,5);
//     return 0;
// }

//二维数组传参,形参是指针的形式
// void Printf(int (*p)[5],int r,int c)
// {
//     int i = 0;
//     for(i = 0; i < r; i++)
//     {
//         int j = 0;
//         for (j = 0; j < c; j++)
//         {
//             printf("%d ",*(*(p+i)+j));
//         }
//     }
// }
// //二维数组的每一行可以理解为二维数组的一个元素
// //每一行又是一个一维数组
// //二维数组其实是一维数组的数组
// //二维数组的数组名,也是数组名,数组名就是数组首元素地址
// //arr - 首元素地址    - 第一行的地址   -一维数组的地址 -数组的地址
// int main()
// {
//     int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
//     //arr数组名是首元素地址
//     Printf(arr,3,5);

//     return 0;

// }


//一维数组传参,形参的部分可以是数组,也可以是指针

// void test1(int arr[5],int sz)
// {

// }

// void test2(int* p,int sz)
// {

// }

// int main()
// {
//     int arr[5] = {0};
//     test1(arr,5);
//     test2(arr,5);

//     return 0;
// }

//二维数组传参,形参的部分可以数组,也可以是指针
void test3(char arr[3][5],int r,int c)
{
}

void test4(char(*p)[5],int r,int c)
{
}
int main()
{
    char arr[3][5] = {0};
    test3(arr,4,5);
    test4(arr,3,5);
    return 0;
}

三、指针数组

指针数组

整型数组-存放整型的数组

字符数组-存放字符的数组

指针数组-存放指针的数组

// int main()
// {
//     int* arr[10];//存放整型指针的数组
//     char* arr2[10]; //一级字符指针的数组
//     char **arr3[10];//二级字符指针的数组

//     return 0;
// }

//使用指针数组模拟实现二维数组
int main()
{
    int arr1[] = {1,2,3,4,5};
    int arr2[] = {2,3,4,5,6};
    int arr3[] = {3,4,5,6,7};

    //指针数组
    int* arr4[3] = {arr1,arr2,arr3};
    printf("%d\n",arr4[1][1]);

    int i = 0;
    for ( i = 0; i < 3; i++)
    {
        /* code */
        int j = 0;
        for(j=0;j<5;j++)
        {
            printf("%d ",arr4[i][j]);
            //printf("%d ",*(arr4+i));
        }
        printf("\n");
    }
    

    return 0;
}

四、数组传参和指针传参

在写代码的时候难免要把数组或者指针传给函数,那函数的参数该如何设计呢?

一维数组传参

二维数组传参

一级指针传参

二级指针传参


//二维数组传参,形参是二维数组的形式
// void Printf(int arr[3][5],int r,int c)
// {
//     int i = 0;
//     for (i = 0; i<3;i++)
//     {
//         int j = 0;
//         for(j = 0;j < 5;j++)
//         {
//             printf("%d ",arr[i][j]);
//         }
//         printf("\n");
//     }
// }
// int main()
// {
//     int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
//     Printf(arr,3,5);
//     return 0;
// }

//二维数组传参,形参是指针的形式
// void Printf(int (*p)[5],int r,int c)
// {
//     int i = 0;
//     for(i = 0; i < r; i++)
//     {
//         int j = 0;
//         for (j = 0; j < c; j++)
//         {
//             printf("%d ",*(*(p+i)+j));
//         }
//     }
// }
// //二维数组的每一行可以理解为二维数组的一个元素
// //每一行又是一个一维数组
// //二维数组其实是一维数组的数组
// //二维数组的数组名,也是数组名,数组名就是数组首元素地址
// //arr - 首元素地址    - 第一行的地址   -一维数组的地址 -数组的地址
// int main()
// {
//     int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
//     //arr数组名是首元素地址
//     Printf(arr,3,5);

//     return 0;

// }


//一维数组传参,形参的部分可以是数组,也可以是指针

// void test1(int arr[5],int sz)
// {

// }

// void test2(int* p,int sz)
// {

// }

// int main()
// {
//     int arr[5] = {0};
//     test1(arr,5);
//     test2(arr,5);

//     return 0;
// }

//二维数组传参,形参的部分可以数组,也可以是指针
// void test3(char arr[3][5],int r,int c)
// {
// }

// void test4(char(*p)[5],int r,int c)
// {

// }
// int main()
// {
//     char arr[3][5] = {0};
//     test3(arr,4,5);
//     test4(arr,3,5);
//     return 0;
// }

//一维数组传参
// void test(int arr[])  //true
// {}
// void test(int arr[10])  //true
// {}
// void test(int* arr) //true
// {}
// void test2(int* arr[20]) //true
// {}
// void test2(int** arr)
// {}

// int main()
// {
//     int arr[10] = {0};   //存放整型
//     int *arr2[20] = {0};  //存放地址
//     test(arr);
//     test2(arr2);
// }

//一级指针传参
// 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;
// }

//二级指针传参
void test(int** ptr)
{
    printf("num = %d\n",**ptr);
}

int main()
{
    int n = 10;
    int* p = &n;
    int** pp = &p;
    test(pp);  //10
    test(&p);  //10

    return 0;
}

五、函数指针

函数指针-指向函数的指针

数组指针-指向函数的指针

//函数指针
// int Add(int x,int y)
// {
//     return x+y;
// }

// int main()
// {
//     int arr[10] = {0};
//     int (*pa)[10] = &arr;
//     // //&arr;
//     // printf("%p\n",&Add);
//     // printf("%p\n",Add);
//     //函数名是函数的地址
//     //&函数名也是函数的地址

//     int (*pf)(int,int) =&Add; //pf是函数指针变量   int(*)(int int)是函数指针类型
// }

// void test(char* pc,int arr[10])
// {}

// int main()
// {
//     void (*pf)(char*,int[10]) = &test;

//     return 0;
// }


int Add(int x,int y)
{
    return x+y;
}

int main()
{
    //int (*pf)(int,int) = &Add;
    int (*pf)(int,int) = Add;
    int r = Add(3,5);
    printf("%d\n",r);

    int m = (*pf)(4,5);
    printf("%d\n",m);


    return 0;
}

//void (*p)() -p是函数指针
//void (*)()是函数指针类型
// int main()
// {
//     //调用0地址处的函数
//     //1.将0强制类型转换为void(*)() 类型的函数指针
//     //2.调用0地址处的这个函数
//     ( *( void (*)())0)();
// }

// typedef unsigned int uint;
// typedef int* ptr_t;

// //typedef int(*)[10] parr_t; //err
// typedef int(*parr_t)[10] ;  //true
// typedef int (*pf_t)(int,int);
// int main()
// {
//     uint u1;
//     ptr_t p1;
//     int* p2;
//     //parr_t[10];
//     //pf_t(2,3);

//     return 0;
// }

int main()
{
    //signal是一个函数声明
    //signal函数有2个参数,第一个参数是int,第二个参数类型是void(*)(int) 函数指针类型
    //该函数指针指向的函数有一个int类型的参数,返回类型是void
    //signal 函数的返回类型也是 void(*)(int) ,该函数指针指向的函数有一个int类型的参数,返回类型是void
    typedef void(*pf_t)(int);
    pf_t signal(int ,pf_t);
    void (* signal(int,void(*)(int)))(int);

    return 0;
}

六、函数指针数组

整型指针放在一个数组中

int* arr[5];  //整型指针数组

char* arr2[5];//字符指针数组

函数指针数组:数组的每个元素是函数指针类型

//函数指针数组
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 (*pf1)(int , int) = Add;
//     int (*pf2)(int , int) = Sub;
//     int (*pf3)(int , int) = Mul;
//     int (*pf4)(int , int) = Div;
//     //函数指针数组
//     int (*pfArr[4])(int , int) = {Add,Sub,Mul,Div};

//     return 0;
// }

void menu()
{
    printf("**********************************\n");
    printf("*******  1.add  2.sub ************\n");
    printf("*******  3.mul  4.div ************\n");
    printf("*******        0.exit ************\n");
    printf("**********************************\n");
}

// 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("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("选择错误重新选择");
//             break;
//         }
//     } while(input);

//     return 0;
// }

//函数指针的方式
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    //函数指针数组的使用 - 转移表
    int (*pfArr[5])(int,int) = {NULL,Add,Sub,Mul,Div};
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d",&input);
        if(input >=1 && input <=4)
        {
            printf("请输入两个操作数:");
            scanf("%d %d",&x,&y);
            ret = pfArr[input](x,y);
            printf("ret = %d\n",ret);
        }
        else if(input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("选择错误,重新选择\n");
        }
        
    } while(input);

    return 0;
}

七、指向函数指针数组的指针

int(*pf)(int,int);//函数指针

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

int(*(*p)[4](int,int) = &pfArr);//函数指针数组的地址

p就是指向函数指针数组的指针

void test(const char* str)
{
    printf("%\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;
}

八、回调函数

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

//回调函数
// 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()
// {
//     printf("**********************************\n");
//     printf("*******  1.add  2.sub ************\n");
//     printf("*******  3.mul  4.div ************\n");
//     printf("*******        0.exit ************\n");
//     printf("**********************************\n");
// }

// void Calc(int (*pf)(int x,int y))
// {
//     int x = 0;
//     int y = 0;
//     int ret = 0;
//     printf("请输入两个操作数:\n");
//     scanf("%d %d",&x,&y);
//     ret = pf(x,y);
//     printf("ret = %d\n",ret);

// }

// int main()
// {
//     int input = 0;
//     int x = 0;
//     int y = 0;
//     int ret = 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("退出计算器\n");
//             break; 
//         default:
//             printf("选择错误重新选择");
//             break;
//         }
//     } while(input);

//     return 0;
// }

//冒泡排序         
//有一组整数,需要排序为升序
//1.两两相邻的元素比较
//2.如果不满足顺序就交换

// int main()
// {
//     int arr[10] = {9,8,7,6,5,4,3,2,1,0};
//     int sz = sizeof(arr)/sizeof(arr[0]);
//     int i = 0;
//     //int a = qsort(arr[10]);
//     //bubble_sort(arr,sz);
//     //趟数
//     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;
//             }
//         }
//     }

//     for (i = 0; i<sz;i++)
//     {
//         printf("%d ",arr[i]);
//     }



//     return 0;
// }

//qsort  函数的特点
//1.快速排序的方法quick
//2.适用于任何类型数据的排序

// void qsort(void *__base, //指向了需要排序的数组的第一个元素
//             size_t __nel, //排序的元素个数
//             size_t __width,//一个元素的大小,单位是字节
// 	    int (* _Nonnull __compar)(const void *, const void *));//函数指针类型-这个函数指针指向的函数,能够比较base指向数组中的两个元素

int cmp_int(const void*p1,const void*p2)
{
   return  (*(int*)p1 - *(int*)p2);
}
void print(int arr[],int sz)
{
    int i = 0;
    for(i = 0;i<sz;i++)
    {
        printf("%d ",arr[i]);
    }
}

//测试qsort排序整型数据
void test1()
{
    int arr[10] = {9,8,7,6,5,4,3,2,1};
    int sz = sizeof(arr)/sizeof(arr[0]);
    //默认是升序的
    qsort(arr,sz,sizeof(int),cmp_int);
    print(arr,sz);
}


//测试qsort排序结构体数据
struct Stu
{
    char name[20];
    int age;
};

int cmp_stu_by_age(const void* p1,const void* p2)
{
    return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

int cmp_stu_by_name(const void* p1,const void* p2)
{
    return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}

void test2()
{
    struct Stu arr[] = { {"张三",18}, {"李四,20"},{"王五,16"}};
    int sz = sizeof(arr)/sizeof(arr[0]);
    qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);
}

void test3()
{
    struct Stu arr[] = { {"张三",18}, {"李四,20"},{"王五,16"}};
    int sz = sizeof(arr)/sizeof(arr[0]);
    qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);
}


int main()
{
    //test1();
    //test2();
    test3();

    return 0;
}

//void* 指针
//void*类型的指针可以接受任意类型的地址
//这种类型的指针是不能直接解引用操作的
//也不能直接进行指针运算的

// int main()
// {
//     int a = 10;
//     float f = 3.14f;
//     int* pa = &a;
//     char* pc = &a;
//     void* pv = &a;

// }

//使用冒泡排序的思想,实现一个功能类似qsort函数 bubble_sort()

//1.使用冒泡排序的思想

//2.适用于任意类型数据的排序

//使用冒泡排序的思想,实现一个功能类似qsort函数   bubble_sort()
//1.使用冒泡排序的思想
//2.适用于任意类型数据的排序

void Swap(char* buf1,char* buf2,int size)//交换arr[j],arr[j+1]这两个元素
{
    int i = 0;
    int tmp = 0;
    for(i = 0;i < size;i++)
    {
        tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        //char*指针一次只能交换一个字节,但是char有四个字节
        buf1++;
        buf2++;
    }
}

void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{
    int i = 0;
    //趟数
    for (i = 0; i < num - i; i++)
    {
        int j = 0;
        //一趟内部比较的对数
        for(j = 0; j < num - 1; j++)
        {
            //假设需要升序,cmp返回>0,交换
            if(cmp((char*)base+j*size,(char*)base+(j+1)*size) > 0)    //两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
            {
                //交换
                Swap((char*)base + j * size,(char*)base + (j+1) * size,size);
            }
        }
    }
}

int cmp_int(const void*p1,const void*p2)
{
   return  (*(int*)p1 - *(int*)p2);
}


//测试bubble_sort()排序整型数据
void test1()
{
    int arr[10] = {9,8,7,6,5,4,3,2,1};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
}

//测试bubble_sort()排序结构体数据

struct Stu
{
    char name[20];
    int age;
};


int cmp_stu_by_age(const void* p1,const void* p2)
{
    return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

int cmp_stu_by_name(const void* p1,const void* p2)
{
    return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}

void test2()
{
    struct Stu arr[] = { {"张三",18}, {"李四,20"},{"王五,16"}};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);
}

void test3()
{
    struct Stu arr[] = { {"zhangsan",18}, {"lisi,20"},{"wangwu,16"}};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);
}

int main()
{
    //test1();
    //test2();
    test3();
    return 0;
}

九、指针和数组面试题的解析

// 数组名的理解

// 数组名是数组首元素的地址

// 但是有2个例外:

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

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

// int main()

// {

// //一维数组

// int a[] = {1, 2, 3, 4};

// printf("%d\n", sizeof(a)); // 4*4=16

// printf("%d\n", sizeof(a + 0)); //数组名a是数组首元素的地址,a+0还是首元素地址,地址的大小4/8

// printf("%d\n", sizeof(*a));//数组名a是数组首元素的地址,*a就是首元素,int类型大小4个字节

// printf("%d\n", sizeof(a + 1));//数组名a是数组首元素的地址,a+1是第二个元素的地址,地址的大小是4/8

// printf("%d\n", sizeof(a[1]));//4

// printf("%d\n", sizeof(&a));//&a是数组的地址,数组的地址也是地址,是4/8个字节

// printf("%d\n", sizeof(*&a));// --> sizeof(a) ,*&a,取地址解引用a就是a 16

// printf("%d\n", sizeof(&a + 1)); //8

// printf("%d\n", sizeof(&a[0]));// 首元素地址 4/8字节

// printf("%d\n", sizeof(&a[0] + 1));//&a[0]是首元素的地址,&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)); //arr表示数组首元素地址,arr+0还是数组首元素地址,地址就是4/8字节

// printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr是首元素,大小1个字节

// printf("%d\n", sizeof(arr[1]));//1

// printf("%d\n", sizeof(&arr));//&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)); //因为字符数组arr中没有\0,所以求字符串长度的时候,会一直往后找,产生的结构就是随机值

printf("%d\n", strlen(arr + 0));// arr+0 是首元素的地址,和第一个一样,也是随机值

//printf("%d\n", strlen(*arr));//arr是数组首元素的地址,*arr就是数组首元素 就是'a'-97

//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,‘a’的ASCII码值就是97,那就是将97作为地址传参

//strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了

//printf("%d\n", strlen(arr[1])); //err

printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,传递给strlen函数后,依然是从数组的第一个元素的位置,还是往后统计

printf("%d\n", strlen(&arr + 1));//跳过这个数组 ,随机值

printf("%d\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,随机值-1

}

#include <stdio.h>
#include <string.h>

// 数组名的理解
// 数组名是数组首元素的地址
// 但是有2个例外:
// 1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
// 2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址

// int main()
// {
//     //一维数组
//     int a[] = {1, 2, 3, 4};
//     printf("%d\n", sizeof(a)); // 4*4=16
//     printf("%d\n", sizeof(a + 0)); //数组名a是数组首元素的地址,a+0还是首元素地址,地址的大小4/8
//     printf("%d\n", sizeof(*a));//数组名a是数组首元素的地址,*a就是首元素,int类型大小4个字节
//     printf("%d\n", sizeof(a + 1));//数组名a是数组首元素的地址,a+1是第二个元素的地址,地址的大小是4/8
//     printf("%d\n", sizeof(a[1]));//4
//     printf("%d\n", sizeof(&a));//&a是数组的地址,数组的地址也是地址,是4/8个字节
//     printf("%d\n", sizeof(*&a));// -->  sizeof(a) ,*&a,取地址解引用a就是a   16
//     printf("%d\n", sizeof(&a + 1)); //8
//     printf("%d\n", sizeof(&a[0]));// 首元素地址 4/8字节
//     printf("%d\n", sizeof(&a[0] + 1));//&a[0]是首元素的地址,&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)); //arr表示数组首元素地址,arr+0还是数组首元素地址,地址就是4/8字节
//     // printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr是首元素,大小1个字节
//     // printf("%d\n", sizeof(arr[1]));//1
//     // printf("%d\n", sizeof(&arr));//&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)); //因为字符数组arr中没有\0,所以求字符串长度的时候,会一直往后找,产生的结构就是随机值
//     printf("%d\n", strlen(arr + 0));// arr+0 是首元素的地址,和第一个一样,也是随机值
//     //printf("%d\n", strlen(*arr));//arr是数组首元素的地址,*arr就是数组首元素 就是'a'-97
//     //strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,‘a’的ASCII码值就是97,那就是将97作为地址传参
//     //strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了
//     //printf("%d\n", strlen(arr[1])); //err
//     printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,传递给strlen函数后,依然是从数组的第一个元素的位置,还是往后统计
//     printf("%d\n", strlen(&arr + 1));//跳过这个数组 ,随机值
//     printf("%d\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,随机值-1
//     return 0;
// }

// int main()
// {
//     char arr[] = "abcdef"; //[a b c d e f \0]
//     // printf("%d\n", sizeof(arr));  //7
//     // printf("%d\n", sizeof(arr + 0)); //arr+0是首元素地址 4/8
//     // printf("%d\n", sizeof(*arr)); // *arr首元素  char类型是1
//     // printf("%d\n", sizeof(arr[1]));//下标1的元素b   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)); //err
//     //printf("%d\n", strlen(arr[1])); //err
//     printf("%d\n", strlen(&arr)); //6
//     printf("%d\n", strlen(&arr + 1));//随机值
//     printf("%d\n", strlen(&arr[0] + 1));//5
//     return 0;
// }

// int main()
// {
//     char *p = "abcdef"; //[a b c d e f \0]
// //     printf("%d\n", sizeof(p));  //p是一个指针变量,大小就要4/8个字节
// //     printf("%d\n", sizeof(p + 1)); //p+1是b的地址,是地址大小就是4/8个字节
// //     printf("%d\n", sizeof(*p)); //*p首元素,字符a  1个字节
// //     printf("%d\n", sizeof(p[0]));//p[0] = *(p+0) = *p  1
// //     printf("%d\n", sizeof(&p)); //   4/8  p = char*  &p = char**
// //     printf("%d\n", sizeof(&p + 1));// 4/8
// //     printf("%d\n", sizeof(&p[0] + 1));// 4/8

//     printf("%d\n", strlen(p)); //6
//     printf("%d\n", strlen(p + 1));//5
//     //printf("%d\n", strlen(*p));// err *p = a
//     //printf("%d\n", strlen(p[0]));// p[0] = *(p+0) = *p err
//     printf("%d\n", strlen(&p));//随机值
//     printf("%d\n", strlen(&p + 1));//随机值
//     printf("%d\n", strlen(&p[0] + 1));//&p[0]+1 是b  5
    //return 0;
// }

int main()
{ 
    // 二维数组
    int a[3][4] = {0}; 
    printf("%d\n", sizeof(a)); //3*4*4
    printf("%d\n", sizeof(a[0][0]));//4
    printf("%d\n", sizeof(a[0])); //4*4
    printf("%d\n", sizeof(a[0] + 1)); // 4/8
    //a[0]作为第一行的数组名,没有单独放在sizeof内部,没有&
    //a[0]表示数组首元素的地址,也就是a[0][0]的地址
    //a[0]+1是第一行第二个元素的地址
    //地址就是4/8
    printf("%d\n", sizeof(*(a[0] + 1)));//计算的就是第一行第2个元素的大小  4个字节
    printf("%d\n", sizeof(a + 1)); // 4/8
    //a是数组首元素的地址,是第一行的地址
    //a+1就是第二行的地址
    printf("%d\n", sizeof(*(a + 1)));  //4*4
    //*(a+1)--> a[1]  -> sizeof(a[1]),计算第二行的大小
    //a+1就是第二行的地址,*(a+1)计算第二行的大小
    printf("%d\n", sizeof(&a[0] + 1));// 4/8
    //&a[0]第一行的地址
    //&[0]+1第二行的地址
    printf("%d\n", sizeof(*(&a[0] + 1)));  //第二行的大小 4*4
    printf("%d\n", sizeof(*a));  //4*4
    //a是数组首元素地址,就是第一行地址
    //*a就是第一行
    //*a -->  *(a+0)   --> a[0]
    printf("%d\n", sizeof(a[3])); //4*4
    //a[3] --> int[4]

    return 0;
}

//总结:数组名的意义

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

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

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

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

// 笔试题2
// 由于还没学习结构体,这里告知结构体的大小是20个字节
// struct Test
// {
//     int Num;
//     char *pcName;
//     short sDate;
//     char cha[2];
//     short sBa[4];
// } *p = (struct Test*)0x100000;
// // 假设p 的值为0x100000。 如下表表达式的值分别为多少?
// int main()
// {
//     printf("%p\n", p + 0x1); //0x100020  +1跳过一个结构体就是20个字节
//     printf("%p\n", (unsigned long)p + 0x1);//0x100001
//     printf("%p\n", (unsigned int *)p + 0x1); //0x100004
//     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\n,%x\n", 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]; //a[0] -->  &a[0][0]
//     printf( "%d", p[0]);//p[0] --> *(p+0) -->*p   1
//     return 0;
// }

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

//笔试题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));//10 5
//     return 0;
// }

//笔试题7
// int main()
// {
//     char *a[] = {"work","at","alibaba"};//char* work   char* at   char* alibaba  
//     char**pa = a;
//     pa++;  //0 work  1 at  2 alibaba
//     printf("%s\n", *pa);  //at
//     return 0;
// }

//笔试题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);
    printf("%s\n", *--*++cpp+3);
    printf("%s\n", *cpp[-2]+3);
    printf("%s\n", cpp[-1][-1]+1);
    return 0;
}

笔试题3

笔试题5

 

 笔试题6

总结:本章主要对指针在初阶的基础上更深一步的进行了研究,对指针地址和解引用都有详细的讲解,如需初阶可以观看C语言基础-指针_小凡喜编程的博客-CSDN博客

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值