C语言进阶——指针

简介: C语言进阶——指针

image.png

1. 字符指针

字符指针-指向字符的指针-存放字符的地址

 

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

常量字符串存放在某一个内存里,常量字符串内容是无法更改的

image.png

#define _CRT_SECURE_NO_WARNINGS 1
 
#include <stdio.h>
 
int main()
{
  char ch = 'w';
  char* pc = &ch;
 
 const char* p = "abcdef";//将字符串的首字符a的地址赋值给*p
  printf("%s\n", p);
  printf("%c\n", *p);//char*的指针只访问首字符的地址
 
  //*p = 'e';
 
           //[abcdef\0]
           //char arr[] = "abcdef";
 
  //printf("%c\n", "abcdef"[3]);
 
  return 0;
}
#include <stdio.h>
int main()
{
  char str1[] = "hello word.";
  char str2[] = "hello word.";
  const char* str3 = "hello word.";
  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");
 
  if (&str3 == &str4)
  {
    printf("Yes\n");
  }
  else
  {
    printf("No\n");
  }
 
  return 0;
}

str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,

当几个指针,指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4值相同,地址不同

image.png

2. 指针数组

指针数组-本质是数组-是存放指针的数组

 

1.概念介绍

image.png

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

2.指针数组运用场景

1.

int main()
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
          //int*  int*  int*
  //指针数组
  int* arr[] = { arr1, arr2, arr3 };
  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");
  }
 
  return 0;
}

image.png

image.png

2.

int main()
{
  //指针数组
  char* arr[5] = {"hello", "hehe", "xihuan", "jjjj", "C++"};
 
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    printf("%s\n", arr[i]);
  }
 
  return 0;
}

image.png

3.数组指针

1.数组指针的定义

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

数组指针存放的是数组的地址,而非数组本身

image.png

理解概念


下面哪个是数组指针(   )

A.int** arr[10]

B.int (*arr[10])

C.char *(*arr)[10]

D.char(*)arr[10]


解析:


A是二级指针数组,B是指针数组,C是char *数组的指针,D是char *的数组。只有C是数组指针。


tip:根据优先级看只有C选项优先跟*结合,其他都不是指针,所以直接选C。


数组名的理解

数组名是数组首元素的地址但是存在2个例外:

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


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

2.&数组名VS数组名

&arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

int main()
{
  int arr[10];
  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);//
  printf("%p\n", &arr+1);
 
  //指针类型决定了指针+1,到底+几个字节
 
  return 0;
}

image.png

3.数组指针的使用

int (*p)[10] = &arr; //p是用来存放数组的地址的,p就是数组指针

   

   char* arr2[5];

   char* (*pc)[5] = &arr2;    定义数组指针

int main()
{
  //int * p;
  int arr[10] = { 0 };
 
  //int [10]
  //int * p;
  //int [10] * p;
 
  int (*p)[10] = &arr; //p是用来存放数组的地址的,p就是数组指针
  
  char* arr2[5];
  char* (*pc)[5] = &arr2;
 
  int arr3[] = { 1,2,3 };
  int (*p3)[3] = &arr3;
 
  return 0;
}
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]);
  }

image.png

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

image.png

void print(int (*p)[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 ", p[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} };
  print(arr, 3, 5);
 
  return 0;
}

image.png

image.png

4.部分重点总结

image.png

4.数组参数、指针参数                                                                        

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

2.二维数组传参

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。

因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

image.png

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

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

image.png

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

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

image.png

5.部分重点总结

image.png

5. 函数指针

指向函数的指针-存放的是函数的地址

&函数名就是函数的地址
函数名也是函数的地址

函数名即是地址可以不用写(*pf2)可直接写(pf2)

int ret = (*pf)(3, 5);//通过函数指针调用函数,也可以int ret=pf(3,5)以及int ret=(***pf)(3,5)

利用题目理解概念

1.下面哪个是函数指针?(()

A.int* fun(int a, int b);

B.int(*)fun(int a, int b);

C.int (*fun)(int a, int b);

D.(int *)fun(int a, int n);


ABD没有区别,加的括号没有影响任何优先级,都是返回值为int *的函数,可认为是函数的声明,故选C


2.定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数?下面哪个是正确的?(   )

A.int (*(*F)(int, int))(int)

B.int (*F)(int, int)

C.int (*(*F)(int, int))

D.*(*F)(int, int)(int)


D类型不完整先排除,然后看返回值,B的返回值是int,C的返回值是int *,故选A。判断返回值类型只需要删掉函数名/函数指针和参数列表再看就行了。int (*(*F)(int, int))(int)删掉(*F)(int, int)后剩下int (*)(int),符合题意。


3.在游戏设计中,经常会根据不同的游戏状态调用不同的函数,我们可以通过函数指针来实现这一功能,下面哪个是:一个参数为int *,返回值为int的函数指针( )

A.int (*fun)(int)

B.int (*fun)(int *)

C.int* fun(int *)

D.int* (*fun)(int *)


首先C压根就不是函数指针,先排除,然后D返回值不是int,排除,A的参数不是int *,排除,剩下B了。

image.png

阅读两段有趣的代码:

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

void (*)() 函数指针

上面的代码是在调用0地址处的函数,这个函数没有参数,返回类型是void

1. //代码2
2. void (*signal(int , void(*)(int)))(int);

image.png

6. 函数指针数组

1.函数指针的数组定义

int (* pfArr[4])(int, int) = {&Add, &Sub};//pfArr 是函数指针数组 - 存放函数指针的数组

 
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[4])(int, int) = {&Add, &Sub};//pfArr 是函数指针数组 - 存放函数指针的数组
 
  return 0;
}

2.函数指针数组的用途:转移表

例子:(计算器)

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("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Add(x, y);
      printf("ret = %d\n", ret);
      break;
    case 2:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Sub(x, y);
      printf("ret = %d\n", ret);
      break;
    case 3:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Mul(x, y);
      printf("ret = %d\n", ret);
      break;
    case 4:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Div(x, y);
      printf("ret = %d\n", ret);
      break;
    case 0:
      printf("退出计算器\n");
      break;
    default:
      printf("选择错误, 重新选择\n");
      break;
    }
  } while (input);
 
  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 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);
    //函数指针数组 - 转移表
    int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
    //                          0     1     2   3    4
    if (0 == input)
    {
      printf("退出计算器\n");
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = pfArr[input](x, y);
      printf("ret = %d\n", ret);
    }
    else
    {
      printf("选择错误,重新选择!\n");
    }
  } while (input);
 
  return 0;
}

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

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

image.png

8. 回调函数

1.概念

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

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

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

条件进行响应。


题目概念理解


关于回调函数描述错误的是()


A.回调函数就是一个通过函数指针调用的函数


B.回调函数一般通过函数指针实现


C.回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用。


D.回调函数是调用函数指针指向函数的函数。


解析:


回调函数是一种在编程中常见的概念,它是指将一个函数作为参数传递给另一个函数,并在特定的条件或事件发生时被调用执行的函数。


具体来说,回调函数是作为参数传递给其他函数的函数指针或函数对象。当满足特定条件或事件发生时,调用该函数指针或函数对象,以执行预定义的操作或逻辑。

2.举例说明

void calc(int (*pf)(int,int))

{

   int x = 0;

   int y = 0;

   int ret = 0;

   printf("请输入2个操作数:");

   scanf("%d %d", &x, &y);

   ret = pf(x, y);

   printf("ret = %d\n", ret);

}

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;
  int ret = 0;
  printf("请输入2个操作数:");
  scanf("%d %d", &x, &y);
  ret = pf(x, y);
  printf("ret = %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("退出计算器\n");
      break;
    default:
      printf("选择错误, 重新选择\n");
      break;
    }
  } while (input);
 
  return 0;
}

qsort函数的使用:

void qsort(void* base, //待排序数组的第一个元素的地址

          size_t num, //待排序数组的元素个数

          size_t size,//待排序数组中一个元素的大小

          int (* cmp)(const void* e1, const void* e2)//函数指针-cmp指向了一个函数,这个函数是用                                                                                    来比较两个元素的

                         //e1和e2中存放的是需要比较的两个元素的地址

         );

1. 排序整型数组, 两个整型可以直接使用>比较

2. 排序结构体数组,两个结构体的数据可能不能直接使用>比较

也就是不同类型的数据,比较出大小,方法是有差异的


/void* 类型的指针 - 不能进行解引用操作符,也不能进行+-整数的操作

//void* 类型的指针是用来存放任意类型数据的地址

//void* 无具体类型的指针

//int* char* 指针类型

1.排序整型数组
#include <stdio.h>
#include <stdlib.h>
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
 
//测试qsort排序整型数据
void test1()
{
  int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  print_arr(arr, sz);
  qsort(arr, sz, sizeof(arr[0]), cmp_int);
  print_arr(arr, sz);
}
int main()
{
 
  test1();
  
  return 0;
}
2. 排序结构体数组
1. 按照年龄比较
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
  char name[20];
  int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
  return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
 
void test2()
{
  struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
  
  test2();
  return 0;
}
2.2. 按照名字比较
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct Stu
{
  char name[20];
  int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
 
void test3()
{
  struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
  test3();
  return 0;
}
3.使用【冒泡排序】是的算法,模拟实现一个排序函数,可以排序任意类型的数据
1.int (*cmp)(const void* e1, const void* e2)

比较两个元素的大小,默认为升序

e1是一个指针,存放了一个要比较的元素的地址

e2是一个指针,存放了一个要比较的元素的地址

e1指向的元素>e2指向的元素,返回>0的数字

e1指向的元素==e2指向的元素,返回0

e1指向的元素

整型数组

升序

int cmp_int(const void*e1, const void*e2)
{
  return *(int*)e1 - *(int*)e2;
}

降序

int cmp_int(const void*e1, const void*e2)
{
  return *(int*)e2 - *(int*)e1;
}
结构体数组——年龄
int cmp_stu_by_age(const void* e1, const void*e2)
{
  return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
结构体数组——名字
int cmp_stu_by_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
2.交换函数
void swap(char* buf1, char* buf2, size_t size)
{
  int i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
3.整型数组打印函数
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
4.冒泡排序数组——泛型编程
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
{
  //冒泡排序的趟数
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    //一趟冒泡排序
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //if (arr[j] > arr[j + 1])
      if(cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
      {
        //交换
        swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
      }
    }
  }
}
5.测试函数
1.整型数组
void test1()
{
  int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//升序
  //排序为降序
  int sz = sizeof(arr) / sizeof(arr[0]);
  print_arr(arr, sz);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
  print_arr(arr, sz);
}
2.结构体数组
void test2()
{
  struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
  int sz = sizeof(arr) / sizeof(arr[0]);//3
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

9. 指针和数组面试题的解析

1.一维数组

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

分析

4个元素,每个元素是int类型(4个字节),16

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

16,数组名a单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小单位是字节,

是16字节

printf("%d\n", sizeof(a));

a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,a+0还是首元素的地址

   //是地址大小就是4/8 Byte   X86环境下是4个字节,X64环境下是8个字节

printf("%d\n", sizeof(a + 0));

a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址

   *a 就是 首元素,大小就是4Byte             *a == *(a+0) == a[0]

printf("%d\n", sizeof(*a));

a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,a+1就是第二个元素的地址

   //a+1 == &a[1]  是第2个元素的地址,是地址就是4/8个字节

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

a[1]就是数组的第二个元素,这里计算的就是第二个元素的大小,单位是字节 - 4

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

&a - 是取出数组的地址,但是数组的地址也是地址,是地址就是4/8个Byte

   数组的地址 和 数组首元素的地址 的本质区别是类型的区别,并非大小的区别


//a  -- int*             int * p = a;

   //&a -- int (*)[4]       int (*p)[4] = &a;


printf("%d\n", sizeof(&a));

对数组指针解引用访问一个数组的大小,单位是字节

   //sizeof(*&a) --- sizeof(a) //16

printf("%d\n", sizeof(*&a));

&a数组的地址,&a+1还是地址,是地址就是4/8个字节

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

&a[0]是首元素的地址, 计算的是地址的大小 4/8 个字节

printf("%d\n", sizeof(&a[0]));

&a[0]是首元素的地址,&a[0]+1就是第二个元素的地址,大小4/8个字节

   //&a[1]

   //&a[0]+1

   //a+1

 

printf("%d\n", sizeof(&a[0] + 1));

2.字符数组

6个元素,每个元素是char类型(1个字节),6

char arr[] = { 'a','b','c','d','e','f' };

数组名arr单独放在sizeof内部,计算的是整个数组的大小,单位是字节

printf("%d\n", sizeof(arr));

arr是首元素的地址==&arr[0],是地址就是4/8个字节

printf("%d\n", sizeof(arr + 0));

char*

指针变量的大小和类型无关,不管什么类型的指针变量,大小都是4/8个字节

指针变量是用来存放地址的,地址存放需要多大空间,指针变量的大小就是几个字节

32位环境下,地址是32个二进制位,需要4个字节,所以指针变量的大小就是4个字节

64位环境下,地址是64个二进制位,需要8个字节,所以指针变量的大小就是8个字节

门缝里看指针,把指针给看扁了

arr是首元素的地址,*arr就是首元素,大小就是1Byte

printf("%d\n", sizeof(*arr));

首元素大小,大小就是1Byte

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

&arr是数组的地址,sizeof(&arr)就是4/8个字节

printf("%d\n", sizeof(&arr));

&arr+1 是跳过数组后的地址,是地址就是4/8个字节

printf("%d\n", sizeof(&arr + 1));

第二个元素的地址,是地址就是4/8Byte

printf("%d\n", sizeof(&arr[0] + 1));

strlen 求字符串长度

统计的是在字符串中\0之前出现的字符的个数

调用的时候是传递地址

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

随机值>=6,arr是首元素的地址

printf("%d\n", strlen(arr));

随机值>=6,arr是首元素的地址, arr+0还是首元素的地址

printf("%d\n", strlen(arr + 0));

err,arr是首元素的地址, *arr就是首元素 - 'a' - 97

站在strlen的角度,认为传参进去的'a'-97就是地址,97作为地址,直接进行访问,就是非法访问

printf("%d\n", strlen(*arr));

err, 'b' - 98

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

随机值,会报警告

&arr -- 取地址 取出来的形式 char (*)[6]

strlen 的参数类型 const char*

printf("%d\n", strlen(&arr));

随机值,不一定大于等于12,'/0'地址不确定

printf("%d\n", strlen(&arr + 1));

随机值

printf("%d\n", strlen(&arr[0] + 1));
//int main()
//{
//  char arr[] = "abcdef";
// a b c d e f /0
// 字符串末尾是有“/0”
//  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
//
//  //a b c d e f \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
//
//  return 0;
//}
int main()
{
  char* p = "abcdef";
 
  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));随机值
  printf("%d\n", strlen(&p + 1));随机值
  printf("%d\n", strlen(&p[0] + 1));5
 
 
  printf("%d\n", sizeof(p));//4/8 计算的是指针变量的大小
  printf("%d\n", sizeof(p + 1));//p+1还是地址,大小是4/8个字节
  printf("%d\n", sizeof(*p));//1个字节, *p == 'a'
  printf("%d\n", sizeof(p[0]));//1个字节, p[0]--> *(p+0) --> *p == 'a';
 
  printf("%d\n", sizeof(&p));//4/8个字节,&p 是地址int * *p=&p
  printf("%d\n", sizeof(&p + 1));//&p是地址,&p+1还是地址,是地址就是4/8个字节
 
  printf("%d\n", sizeof(&p[0] + 1));
 
 
 
  return 0;
}

3.二维数组

int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));

48-数组名a单独放在了sizeof内存,表示整个数组,sizeof(a)计算的是数组的大小,单位是字节

printf("%zd\n", sizeof(a));

4-a[0][0]是数组的第一行第一个元素,这里计算的就是一个元素的大小,单位是字节

printf("%zd\n", sizeof(a[0][0]));

16 - a[0]是第一行这个一维数组的数组名,数组名单独放在了sizeof内部

   a[0]就表示整个第一行这个一维数组,sizeof(a[0])计算的整个第一行这个一维数组的大小

printf("%zd\n", sizeof(a[0]));

4 - a[0] + 1是第一行第二个元素的地址,*(a[0] + 1))就是第一行第二个元素

printf("%zd\n", sizeof(*(a[0] + 1)));

4/8

   //a 作为二维数组的数组名,并没有单独放在sizeof内部,也没有&,a就是数组首元素的地址,也就是第一行的地址, a 的类型是 int(*)[4]

   //a+1 就是第二行的地址,类型是:int(*)[4]

printf("%zd\n", sizeof(a + 1));

16 a+1是第二行的地址,*(a+1)就是第二行,计算的就是第二行的大小

   //另外一个角度理解:*(a+1) -- a[1]

   //sizeof(a[1]) - a[1]这个第二行的数组名,单独放在了sizeof内部,计算的是第二行的大小


printf("%zd\n", sizeof(*(a + 1)));

4/8

   //a[0]是第一行的数组名,&a[0]取出的是数组的地址,取出的是第一行这个一维数组的地址,类型就是int(*)[4]

   //&a[0]+1 就是第二行的地址,类型就是int(*)[4]

printf("%zd\n", sizeof(&a[0] + 1));

16——*(&a[0] + 1)得到的就是第二行,计算的就是第二行的大小

printf("%zd\n", sizeof(*(&a[0] + 1)));

16——a表示数组首元素的地址,也就是第一行的地址

   //*a 就是第一行,也就相当于是第一行的数组名

   //*a--> *(a+0) -- a[0]

printf("%zd\n", sizeof(*a));

16-不会越界,编译器不会真的去访问第四行而是根据类型确定大小

   //a[3]和  arr[0]是一样的

   //都表示数组 int [4]   表达式 有2个属性:值属性,类型属性

printf("%zd\n", sizeof(a[3]));

数组名的意义:

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

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

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


相关文章
|
2天前
|
存储 C语言
C语言的函数返回值和指针
C|函数返回值(区分各类值)和指针(区分各类存储空间)的细节
|
2天前
|
C语言
【C语言】:详解函数指针变量,函数指针数组及转移表
【C语言】:详解函数指针变量,函数指针数组及转移表
8 2
|
2天前
|
C语言
【C语言】:详解指针数组,数组指针及(二维)数组传参(2)
【C语言】:详解指针数组,数组指针及(二维)数组传参(2)
6 1
|
2天前
|
Serverless C语言
【C语言】:对(一维)数组与指针的深入理解(1)
【C语言】:对(一维)数组与指针的深入理解(1)
5 1
|
5天前
|
C语言
C语言----深入理解指针(5)(一)
C语言----深入理解指针(5)
10 2
|
5天前
|
C语言
C语言---深入指针(4)(二)
C语言---深入指针(4)
12 2
|
5天前
|
C语言
C语言---深入指针(4)(一)
C语言---深入指针(4)
|
1天前
|
存储 C语言 C++
【C语言刷题系列】水仙花数的打印及进阶
【C语言刷题系列】水仙花数的打印及进阶
|
2天前
|
安全 C语言
【C语言】:野指针和assert断言
【C语言】:野指针和assert断言
7 0
|
2天前
|
C语言
【C语言】:深入理解指针变量
【C语言】:深入理解指针变量
5 0