C语言入门05——指针

1.指针是什么

  • 指针是内存中最小单元的编号,也就是地址
  • 平时说的指针其实是指针变量,用来存放内存地址的变量

(内存的地址叫指针,内存以字节编号,这个编号就是地址,变量从底地址向高地址存储,存地址的变量就叫指针变量)

在计算机内存中,每一个字节单元,都有一个编号,称为地址

2.指针的大小

对于32位电脑 内存地址从0x0000 0000到oxffff ffff,就可以存0~2^32(4G)-1个地址,cpu和内存之间是地址线相连,一共32根,也就需要32个数据,(查询地址),对应32个bite位(4字节)

对于64位电脑 内存地址从0x0000 0000 0000 0000到oxffff ffff ffff ffff,就可以存0~2^64(8G)-1个地址,cpu和内存之间是地址线相连,一共64根,也就需要64个数据,对应64个bite位(8字节)

3.指针类型的意义(解引用与+-1)

char *pc = NULL;
short *ps = NULL;
int  *pi = NULL;
double *pd = NULL;

printf("zu\n",sizeof(pc));   //4
printf("zu\n",sizeof(ps));   //4
printf("zu\n",sizeof(pi));   //4
printf("zu\n",sizeof(pd));   //4

为啥所占字节一样还要分别定义
指针类型的差异取决于解引用时访问几个字节
如果是int*,解引用访问4个字节
如果是char*,解引用访问1个字节
int a = 0x11223344;
int * pa = &a;
char* pc = (char)&a;
printf("pa = %p\n",pa);    //这里pa和pc是一样的
printf("pa = %p\n",pa+1);  //pa+1是加了4个字节
printf("pc = %p\n",pc);
printf("pc = %p\n",pc+1);  //pc+1是加了1个字节

相同长度的指针也不能混用
int* 与 float*  ,解引用的内容不一样

4.野指针

  • 指针指向的位置是不可知的,随机的,不正确的,没有明确限制的
1.没有初始化
int * p;
*p = 10; //p没有初始化,没有明确的指向,非法访问内存,这里的p就是野指针;
//一个局部变量不初始化,就放的是随机值

2.指针越界访问
int[10] = {0};
int *p =arr;  //这里arr == &arr[0]
for(int i=0;i<10;i++)
{
    *p = i;
     p++;  //当指针指向的范围超出数组arr的范围时,p就是野指针
}

3.指针指向的空间释放
int* test()
{
    int a =10;
    return &a;
} 
int main()
{
    int *p = test();  //这里a一出函数,就被销毁了,
    //但是p还记得a存放的地址,但是p没办法使用该空间
    //当*p时还是会出现10,当这块空间没被占用,还是会出现10的,但这样是随机的
    
    return 0;     
}
  • 如何规避野指针
  • 1. 指针初始化
  • 2. 小心指针越界
  • 3. 指针指向空间释放及时置NULL
  • 4. 避免返回局部变量的地址
  • 5. 指针使用之前检查有效性
#include 
int main()
{    
    int *p = NULL;  //NULL无法访问  
    //....    
    int a = 10;    
    p = &a;    
    if(p != NULL)   
    {        
        *p = 20;   
    }   
    return 0;
}

5.指针运算

  • +-整数运算

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算

//values[N_VALUES]已经超出了数组原来的地址长度了
for (vp = &values[0]; vp < &values[N_VALUES];)  
{     
    *vp++ = 0;  //*vp = 0;vp++;
    //*vp++  vs  (*vp)++
    //(*vp)++(对指向的对象++),先解引用,再将解引用的值++
    //*vp++(对指针++)
}  //values[N_VALUES] = {0,0,0,0,0}

数组赋值
1.方法一
for(int i = 0;i<sz;i++)
{
    arr[i] = i;
}
2.方法二
int *P =arr;
for(int i= 0;i<sz;i++)
{
    *p = i;
    p++;
}
2.方法三
int *P =arr;
for(int i= 0;i<sz;i++)
{
    *(p+i) = i;
}
  • 指针-指针的绝对值(得到的是两指针之间元素的个数)

//不是所有指针都能相减,只有指向同一块内存的指针才可以  
printf("%d\n",&arr[9]-&arr[0]); //9个元素
int my_strlen(char *s)
{       
    char *p = s;       
    while(*p != '\0' )              
    p++;       
    return p-s;
}
  • 指针的关系运算

for(vp = &values[N_VALUES]; vp > &values[0];)
{    
    *--vp = 0;
}

简化版本

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{    
    *vp = 0;
}
//实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

可以与往后越界的指针比较,不能跟往前越界的指针比较

6.指针与数组

  • 数组:一组相同类型元素的集合
  • 指针变量:是一个变量,存放的是地址
int arr[10] = {0};
//arr是首元素的地址(两种情况例外,sizeof,&arr)
//&arr[0]
int *p = arr;
for(int i =0;i<sz;i++)
{
    printf("%d\n",*(p+i));  //printf("%d\n",arr[i]);  //*p++
    //printf("%d\n",*(arr+i));
}

7.二级指针(存放一级指针变量的地址)

int a =10;
int* pa = a; //这里的p是一个一级指针变量,只需要解引用一下  (*pa 就找到了 a)
             //pa在计算机中也有地址,取pa的地址&pa,就找到了pa的地址
int ** ppa =&pa;  // int *说明ppa是指向int *类型的指针,后面的*,表示它ppa是指针
**ppa = 20;  //将a改成20

8.字符指针

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

一般使用:

还有一种使用方式如下:

int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'b';
 return 0;
}

char* p = "abcdef"; //这里讲"abcdef"的首元素的地址赋值给了p
printf("%s\n",p);  //结果:abcdef
* p = 'w';         //这时p的内容不能被改,加上const就可以明显表明不能修改

不要以为是把字符串 abcdef 放到字符指针 p里了,但是/本质是把字符串 abcdef 首字符的地址放到了p中。

上面代码的意思是把一个常量字符串的首字符 a 的地址存放到指针变量 p 中。

那就有可这样的面试题:

#include <stdio.h>
int main()
{
 char str1[] = "hello bit.";         //str1存放的是数组str1[]的首元素地址
 char str2[] = "hello bit.";         //str2存放的是数组str2[]的首元素地址
 const char *str3 = "hello bit.";    //str3存放的是"hello bit."的首元素地址
 const char *str4 = "hello bit.";    //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;
}
这里最终输出的是:  str1和str2不同,str3和str4相同

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。

但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

9.指针数组(存放指针的数组)(主语是数组(好孩子主语是孩子))

指针数组名相当于二级指针 int * p[2] = {a[0],a[1]}; int **q = p;

//存放指针的数组就是指针数组
int a =10;
int b = 20;
int c = 30;
// int* pa = &a;
// int* pb = &b;
// int* pc = &c;
int * parr[10] = {&a,&b,&c};  //指针数组
for(int i = 0; i < 3; i++)
{
    printf("%d ",*(parr[i]));
}

//可以用指针数组模拟一个二维数组
1.常用方法
int arr[3][4] = {1,2,3,4,2,3,4,5,3,4,5,6};
int i = 0 ,j = 0;
for(i = 0 ;i  <3;i++)
{
    for(j=0;j<4;j++)
    {
        printf("%d ",parr[i][j]);    
    }
    printf("\n");
}

2.指针数组模拟
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};

for(i=0;i<3;i++)
{
    for(j=0;j<4;j++)
    {
        printf("%d ",parr[i][j]);   // parr[i][j]等价于 *(parr[i]+j)等价于*((parr+i)+j)
    }   
    printf("\n");
}

 指针数组是一个存放指针的数组。

int*  arr1[10];  //整形指针的数组  [int* int* int* int* int* int* int* int* int* int*]
char* arr2[4];   //一级字符指针的数组[char* char* char* char*]
char**arr3[5];   //二级字符指针的数组[char** char** char** char** char**]


//看成二维数组
int arr1[] ={1,2,3,4,5};
int arr2[] ={2,3,4,5,6};
int arr3[] ={3,4,5,6,7};

int *parr[] ={arr1,arr2,arr3}; 
for(int i =0;i<3;i++)
{
    for(int j =0;j<5;j++)
    {
       printf("%d",*(parr[i] +j));   // 也可以这样写 printf("%d",parr[i][j]); 
    }
    printf("\n");
}

10.数组指针(主语是指针)(行指针)

10.1数组指针的定义(指向数组的指针,用来存放数组的地址的)

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

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

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

int *p1[10];       //int* p1[10];   是指针数组
int (*p2)[10];     //int (*p2)[10]; 是数组指针 p2可以指向一个数组,该数组有10个整形元素
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

int (*p)[10];//解释:p先和结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。//这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

10.2 &数组名VS数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

有两个例外,一个是sizeof(arr),一个是&arr(这里表示整个数组&arr+1直接跳过整个数组的地址并加上一个 数组元素类型的地址)

int

int

........

int

&arr

&arr[last]

&arr+1

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。

(细细体会一下)本例中 &arr 的类型是: int()[10] ,是一种数组指针类型数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.


int arr[10] = {0};
int *p = arr;
int (*p2)[10] = &arr;  //(*p2)这是指针,[10]指向的数组有十个元素

char *arr[5] = {0};
char* (*pc)[5] = &arr;

10.3 数组指针的使用

那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

看代码:

#include <stdio.h>
int main()
{
     int arr[10] = {1,2,3,4,5,6,7,8,9,0};
     int (*p)[10] = &arr;//(这里的10不能少)把数组arr的地址赋值给数组指针变量p
     int i=0;
     sz = sizeof(arr)/sizeof(arr[0]);
     for(i=0;i<sz;i++)
     {
         printf("%d",*(*p+i)); .
         //p是指向数组的,*p其实就相当于数组名,数组名又是首元素的地址,所以*p就是首元素的地址     
     }
     //但是我们一般很少这样写代码
     //正常我们这样写
     int *p = arr;
     for(i=0;i<sz;i++)
     {
         printf("%d",*(p+i));     
     }     
     return 0;
}

一个数组指针的使用:

void print(int arr[3][5],int r,int c)
{
    for(int i =0;i <r;i++)
    {
          for(int j =0;j <c;j++)
          {
              printf("%d",arr[r][c]);          
          }  
           printf(\n"); 
    }
}
void print2(int (*p)[5],int r,int c)
{
    for(int i =0;i<r;i++)
    {
        for(int j=0;j<c;j++)
        {
            printf("%d",*(*(p+i)+j));  //*(p+i)相当于拿到第i行的首地址  也可以写成 (*(p+i))[j]或者p[i][j]
        }    
    }
}
int main()
{
    int arr[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};   //二维数组的首元素是第一行
    print1(arr,3,5);
    print2(arr,3,5);
    return 0;
}

学了指针数组和数组指针来一起回顾并看看下面代码的意思:

int arr[5];            //整型数组{int int int int int}
int *parr1[4];
        //整形指针数组{int* int* int* int*}
int (*parr2)[10];
      //数组指针指指向具有10个整型元素的数组int (*parr2)[10]= arr[10]
int (*parr3[3])[5];   //parr3是存放数组指针的数组
//parr3数组名,[3]有3个整型数组,[5]每个元素5个元素
 arr1[]={1,2,3,4,5};
 arr2[]={2,3,4,5,6};
 arr3[]={3,4,5,6,7};
 int (*parr3[3])[5] ={&arr1,&arr2,&arr3};
 

11.数组参数、指针参数

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

11.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};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

int arr[10] = {0};
void test(int arr[])//ok
void test(int arr[10])//ok
void test(int *arr)//ok

 int *arr2[20] = {0};
 void test2(int *arr2[20])//ok
 void test2(int **arr2)//ok  //*arr2[0]的地址用二级指针存放

11.2 二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])// not ok 
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//not ok
{}
void test(int* arr[5])//not ok
{}
void test(int (*arr)[5])//ok
{}
void test(int **arr)//ok?
{}
void test(int *arr)
这个声明并不适合表示传递一个二维数组,因为它实际上只接受一个指向整数的指针。它适用于传递一维数组或者整数指针。
这个声明表示函数接受一个指向整数的指针作为参数。
void test(int* arr[5])
这个声明也不适合表示传递一个二维数组,因为它实际上是一个指针数组,每个指针指向整数。
这个声明表示函数接受一个包含5个指向整数的指针的数组作为参数。
void test(int (*arr)[5])
这个声明是正确的,它表示函数接受一个指向包含5个整数的数组的指针。
这个声明适合表示传递一个二维数组。
void test(int **arr)
这个声明是合法的,但它不适合表示传递一个二维数组。它实际上是一个指向指针的指针,通常用于动态分配内存的二维数组。
这个声明表示函数接受一个指向指针的指针作为参数,可能用于传递一个指针数组或者二级指针。


int main()
{
 int arr[3][5] = {0};
 test(arr);   //二维数组的数组名表示首元素的地址,其实是第一行的地址(一维数组的地址)
}

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

思考:

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

比如:

int a = 10;
int *p = &a;
int arr[10]={};

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

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

思考:

当函数的参数为二级指针的时候,可以接收什么参数?.

int a = 10;
int *p = &a; 
int **pp = &p;
int* arr[10]={0};  //指针数组

test(pp);
test(&p);
test(arr);

13.函数指针(指向函数的指针)

#include <stdio.h>
void test()
{
     printf("hehe\n");
}
int main()
{
     int arr[10]={0};
     //&数组名 ==== 取出数组的地址
     int(*p)[10] = &arr;        
     //&函数名 ==== 取出函数的地址  ==  函数名
     printf("%p\n", test);   
     printf("%p\n", &test);
     return 0;
}

//函数指针的形式
int add(int x,int y)
{
    return x+y;
}
int (*pf)
(int,int) = &add;    //函数指针的使用,可以直接通过地址来调用函数
//也可以这样写   int pf(int,int) = &add; 或者 int (*********pf)(int,int) = &add;  *只是摆设,让人理解

int ret = (*pf)(2,3)  //ret = 5;
//也可以这样写 int ret = pf(2,3)

( *( void(*)() )0 )();

void(*)()                 //void类型的函数指针(无参数) 类型          
( void(*)() )0            //将0强制类型转换(0是一个地址)
*( void(*)() )0 )         //再对这个地址解引用找到一个函数
 ( *( void(*)() )0 )();   //再对这个函数进行调用
 
 void (*signal(int,void(*)(int)))(int);
 
 void(*)(int)      //这是一个函数指针类型,这个函数是void返回值,参数是int类型
 *signal(int,void(*)(int)) // 将(int,void(*)(int))为参数传给signal函数;signal返回值类型也是函数指针  
 void (*signal(int,void(*)(int)))(int);  //这是一个返回值为void类型,参数为int类型的函数指针
 
 
 简化一下
 typedef void(*pf_t)(int) pf_t;  //把void(* )(int)这样的函数指针类型重命名为pf_t
 
 pf_t(signal(int,pf_t));

13.1函数指针的用途

写一个计算器

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

void  clc(int (*pf)(int,int))
{
    int x,y;
    scanf("%d %d",&x,&y)
    int rey = pf(x,y)
    printf("%d",ret);
}

int main()
{
      clc(add);//实质上是回调函数,通过函数指针,再回头调用这个函数
      return 0;
}

14.函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

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 (*pf)(int,int) = add;
int (*arr[4])(int,int) = {add,sub,mul,div};  //arr就是函数指针的数组

int ret = arr[0](3,4);  //就是在掉用add函数

15函数指针数组的用途(转移表)

/* 计算器实现加减乘除的功能 */

#include <stdio.h>

void menu()
{
    printf("*******************\n");
    printf("*******************\n");
    printf("*** 1.add 2.sub ***\n");
    printf("*** 3.mul 4.div ***\n");
    printf("*** 0.exi       ***\n");
    printf("*******************\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 (*parr[5])(int,int) = {0,add,sub,mul,div};

int main()
{
    int n = 0;

    do
    {
        menu();
        printf("请输入>");
        scanf("%d", &n);
        if(n == 0)
        {
            printf("退出程序\n");
        }
        else if(n >= 1|| n<=4)
        {
            int x,y;
            printf("输入两个操作数\n");
            scanf("%d %d",&x,&y);
            int ret = parr[n](x,y);
            printf("%d\n",ret);
        }
        else
        {
            printf("输入有误\n");
        }
    } while (n);

    return 0;
}

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

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针 ;

int (*parr[5])(int,int) = {0,add,sub,mul,div};
int *(*pparr)[5])(int,int) = &parr;  //&parr数组的地址 pparr指向函数指针数组的指针
*pparr 表示ppar的类型是指针,它指向的对象类型是int (*[5])(int,int)

17.回调函数

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

首先演示一下qsort函数的使用:

使用回调函数,模拟实现qsort(采用冒泡的方式)。

qsort 函数是 C 标准库中的一个函数,用于对数组进行快速排序。它的原型定义在 stdlib.h 头文件中:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

下面是对该函数参数的解释:

  • base:指向要排序的数组的第一个元素的指针。
  • nmemb:数组中元素的个数。
  • size:每个元素的大小(以字节为单位)。
  • compar:指向一个函数的指针,该函数用于比较两个元素的顺序。这个函数接受两个指向常量对象的指针(void 类型),并返回一个整数值,用于指示两个元素的相对顺序。

qsort 函数将按照 compar 函数的定义,对数组 base 中的元素进行排序。排序完成后,base 中的元素将按照 compar 函数的规则排列。

下面是一个简单的示例,演示如何使用 qsort 函数对整数数组进行排序:

#include <stdio.h>
#include <stdlib.h>

// 比较函数,用于指示两个整数的相对顺序(升序)
int compare(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

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

    // 计算数组元素个数
    size_t n = sizeof(arr) / sizeof(arr[0]);

    // 使用 qsort 函数对数组进行排序
    qsort(arr, n, sizeof(int), compare);

    // 打印排序后的数组
    printf("排序后的数组:");
    for (size_t i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

在这个示例中,我们定义了一个 compare 函数,用于比较两个整数的大小。然后我们使用 qsort 函数对整数数组 arr 进行排序,最后打印排序后的结果。

#include <stdio.h>
#include <stdlib.h>

int compare(const void* n1,const void * n2)
{
    return *(int*)n1 - *(int*)n2;
}

int main()
{
    int s[] = {59,23,10,8,7,61} , s, i;
    n = sizeof(s)/sizeof(s[0]);
    
    qsort(s,n,sizeof(int),copare);
    
    for(i = 0; i < n;i++)
    {
        printf("%d\n",s[i]);
    }

    return 0;
}

18.void* 的指针(作用:可以指针处理多种情况,完成不同场合的应用)

注:这里第一次使用 void* 的指针。

int a = 10;
char * p = &a;  //这样是不行的
void* pv = &a;  //这样就可以,void*是无具体类型的指针,所以不能解引用操作,也不能加减整数

要强制类型转换才能解引用
在没有强制 类型转换的时候,不能进行加减运算。

19.const修饰符

  • const修饰的变量不能修改。
  • const int * p = &m; //指针常量,*p不能修改m,不能(*p)++,const修饰*p,*p就不能改
  • int * const p = &m; //常量指针 ,p只能指向m,不能p=&n;const修饰p,p就不能改

20.指针和数组笔试题解析

总结: 数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。
  4. 指针笔试题
  5. *(数组名+1) == 数组名[1] ==*(指针+1)
//一维数组
int a[] = {1,2,3,4};
!!!!printf("%d\n",sizeof(a));    //整个数组所占的字节4*4
printf("%d\n",sizeof(a+0));   //首元素的地址4或者8
printf("%d\n",sizeof(*a));   //首元素的类型4
printf("%d\n",sizeof(a+1));   //第二个元素的地址4或者8
printf("%d\n",sizeof(a[1]));   //第二个元素的类型4
printf("%d\n",sizeof(&a));   //首元素的地址4或者8
!!!!!printf("%d\n",sizeof(*&a));   //
先&取出整个数组的地址,类型是int(*)[4],再解引用*得到整个数组*(int(*)[4]),或者&*抵消,得到a,等于16
printf("%d\n",sizeof(&a+1));   //跳过整数组的地址并且再加一个整形的地址,4或者8
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
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));   //no
!!!!!!!!!!!!printf("%d\n", strlen(arr+0));   //no
!!!!!!!!!!!!printf("%d\n", strlen(*arr));   //
*arr是首元素 === strlen('a') ===== strlen(97);报错(野指针)
printf("%d\n", strlen(arr[1]));   //报错(野指针)
!!!!!!!!!!!!!!printf("%d\n", strlen(&arr));   //no 跟printf("%d\n", strlen(arr))一样
printf("%d\n", strlen(&arr+1));   //no
printf("%d\n", strlen(&arr[0]+1));   //no

 
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));   //7
printf("%d\n", sizeof(arr+0));   //4
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));   //报错(野指针)
printf("%d\n", strlen(arr[1]));   //报错(野指针)
!!!!!!!printf("%d\n", strlen(&arr));   //6
printf("%d\n", strlen(&arr+1));   //no
!!!!!!!!!!!!printf("%d\n", strlen(&arr[0]+1));   //5

 
char *p = "abcdef";
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
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
printf("%d\n", strlen(p[0]));   //err
!!!!!!!!!!printf("%d\n", strlen(&p));   //no  //这是p的地址后面有啥是不知道的
printf("%d\n", strlen(&p+1));   //no
printf("%d\n", strlen(&p[0]+1));   //no

//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));   //48
printf("%d\n",sizeof(a[0][0]));   //4
printf("%d\n",sizeof(a[0]));   //16
printf("%d\n",sizeof(a[0]+1));   //4/8
printf("%d\n",sizeof(*(a[0]+1)));   //4
!!!!!!!!!!!!printf("%d\n",sizeof(a+1));   //4/8(第二行的地址)
!!!!!!!!!!!!printf("%d\n",sizeof(*(a+1)));   //16 相当于a[1];
!!!!!!!!!!!!printf("%d\n",sizeof(&a[0]+1));   //4/8  相当于 a[1]的地址
!!!!!!!printf("%d\n",sizeof(*(&a[0]+1)));   //16  相当于a[1]
printf("%d\n",sizeof(*a));   //16
printf("%d\n",sizeof(a[3]));   //16//a[3]的类型是16

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值