指针详解(const、指针运算、数组名的理解、传址调用和传值调用、一维数组的本质​)(一)

简介: 指针详解(const、指针运算、数组名的理解、传址调用和传值调用、一维数组的本质​)(一)


一、const修饰指针

const修饰指针       const(常量,不变)

问:被const是否修饰的变量是否有其他方法修改值?

答:可以通过指针的方式绕过a修改其值

int main()
{
  const int a = 10;
  int* pa = &a;//虽然说对a进行了限制,但是此处绕过了a,去修改值
  *pa = 0;//虽然达到了效果,但是操作有点不合理
  printf("%d\n", a);
  return 0;
}
问:被const修饰后,变量是否变成了常量?

答:并不是

int main()
{
  const int a = 10;//a不能被修改了,但是a的本质还是变量
  a = 20;//const仅仅是语法上做出了限制,习惯上叫常变量 修改-err
  printf("a = %d\n", a);
  return 0;
}

const修饰指针的时候

1.const可以放在*的左边

2.const可以放在*的右边

1.const放在*的左边
int main()
{
  const int a = 10;
  int const* p = &a;   //限制的是*p
  //意思是不能通过p来修改p指向的空间的内容
  //*p = 0;//err,报错
  int b = 20;
  p = &b;//ok
  return 0;
}
2.const放在*的右边
int main()
{
  const int a = 10;
  int *const p = &a;//const限制的是p,也就是p变量不能被修改,没办法指向其他变量,
  //但是*p不受限制,还是可以通过p来修改p所指向的变量
  *p = 0;//ok
  int b = 20;
  //p = &b;//err,报错
  printf("a = %d\n", a);
  return 0;
}
由上述两段代码可以看出:

1.p里面存放的是地址(a的地址)

2.p是变量,有自己的地址

3.*p是p指向的空间

4.const放在*的左边,限制的是*p,意思是不能通过修改指针变量p修改p指向空间的内容。

*p = 20;//err

但是p是不受限制的      p = &b;//ok

5.const放在*的右边限制的是p变量,也就是p变量不能被修改了,没法指向其他变量了。

p = &b;//err

但是*p不受限制,还是可以通过p来修改p所指向的对象的内容。

*p = 20;//ok

二、指针运算

指针的基本运算有三种:

1、指针 +- 整数

2、指针 +- 指针

3、指针的关系运算

2.1指针与整数的运算

在下例中,指针p加一是指向数组的下一个元素

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  //使用指针打印数组的内容
  int* p = &arr[0];
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    //printf("%d", *p);
    //p++;//指针加整数
    printf("%d\n", *(p + i));//p+i加的是i * sizeof(int)
  }
  //p = 00000098240FF5D4
  //p+0 = 00000098240FF5D4
  //p+1 = 00000098240FF5D8
  return 0;
}
2.2指针与指针的运算

指针减去指针的得到的是他们之间的元素个数的绝对值

指针-指针运算的前提条件的:两个指针指向同一块空间

int main()
{
  //指针 - 指针 = 地址 - 地址
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  printf("%d\n", &arr[0] - &arr[9]);//  |9| 
  char ch[20] = { 0 };
  printf("%d\n", &ch[0] - arr[0]);//err
  return 0;
}
2.3指针的关系运算

其实就是地址的比大小

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);//10
  int* p = &arr[0];
  //arr是数组名,数组名其实是数组首元素的地址,arr <==>&arr[0]
  while (p < arr + sz)//使用while循环打印arr的数组
  {
    printf("%d", *p);
    p++;
  }
  return 0;
}

三、数组名的理解

int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 return 0;
}

根据上述代码输出结果,我们发现数组名和数组首元素的地址打印出的结果一模一样,数组名就是数组首元素(第一个元素)的地址。

int main()
{
  int arr[10] = { 0 };
  printf("%d\n", sizeof(arr));
  printf("%p\n", &arr[0]);//int*
  printf("%p\n", &arr[0] + 1);//+4
  printf("%p\n", arr);//int*
  printf("%p\n", arr + 1);//+4
  //&arr[0]和arr都是首元素的地址,+1就是跳过一个元素
  printf("%p\n", &arr);
  printf("%p\n", &arr+1);//+40
  //因为&arr是数组的地址,+1的操作是跳过整个数组的
  return 0;
}

1.sizeof内部单独放一个数组名的时候,数组名表示的就是整个数组,计算的是整个数组的大小单位是字节。

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

除此之外,遇到的所有数组名都是数组首元素的地址。

3.&arr[0]和arr都是首元素的地址,+1就是跳过一个元素

4.因为&arr是数组的地址,+1的操作是跳过整个数组的

四.使用指针访问数组

1.因为数组在内存中是连续存放的。

2.数组名就是首元素的地址(方便找到起始位置)可以使用指针来访问数组。

代码1(正常写法):

int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int* p = arr;
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    scanf("%d",&arr[i]);
  }
  for (i = 0; i < sz; i++)
  {
    printf("%d",arr[i]);
  }
  return 0;
}

代码2(通过指针解引用把值输出):

scanf("%d", p + i);使用scanf函数从用户接收一个整数,并将其存储在指针p加上i所指向的位置。这实际上是向数组中输入数据。

printf("%d ", *(p + i));使用printf函数输出指针p加上i所指向的整数。这实际上是从数组中读取数据并输出。

int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int* p = arr;
  //p <==> arr
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    scanf("%d", p + i);
  }
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(p + i));
  }
  return 0;
}

代码3

数组加元素的形式,这里使用arr + i来获取数组第i个元素的地址

int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int* p = arr;
  //p <==> arr
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    scanf("%d", arr + i);
  }
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(arr + i));
  }
  return 0;
}

代码4

在C语言中使用i[arr]时,实际上进行了一个指针偏移操作。

1.首先,arr被解析为指向数组首元素的指针。在内存中,数组的元素是连续存储的,而数组名实际上是一个指向数组首元素的指针。因此,通过arr可以找到数组的起始位置。

2.接下来,使用索引i对指针进行偏移。在C语言中,一个指针偏移n个元素就是移动指针到从起始位置开始的第n个元素。因此,通过偏移i个元素,你可以找到数组中第i个元素的位置。

3.arr[i]中的[]是索引运算符,用于访问数组中的元素。它表示将数组名arr解析为指向数组首元素的指针,并使用索引i进行偏移,以访问数组中第i个元素的值。

int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int* p = arr;
  //p <==> arr
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    scanf("%d", &p[i]);
  }
  for (i = 0; i < sz; i++)
  {
    printf("%d ", i[arr]);
  }
  return 0;
}

以上代码说明:

arr[i] == *(arr + i) ==*(i+arr) == i[arr],

&p[i] == &arr[i] == p + i == arr + i,

p[i] == *(p + i)

五、一维数组的本质

引子:我们之前都是在函数外部计算数组的元素个数,把数组传给下一个函数后,函数内部可以求数组元素的个数吗?

数组传参的时候形参是不会创建数组的,实际上传的是首元素的地址,发生了数组降级

void test(int arr[])  //int *arr
{
  int sz = sizeof(arr) / sizeof(arr[0]);
  printf("%d\n", sz);//?
}
void Print(int arr[],int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d", arr[i]);
    //arr[i] == *(arr + i)
  }
}
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);//获取数组中的元素个数
  //test(arr);//这里的数组名就是数组首元素地址
  Print(arr,sz);
  return 0;
}

六、传址调用和传值调用

传值调用:传的是变量,传值调用

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 10;
  int b = 20;
  int ret = Add(a, b);
  //调用函数
  printf("%d\n", ret);
  return 0;
}

传值调用:传的是地址

6.1模拟strlen函数
int my_strlen(const char* s)
//const此处保护arr[]中的值,防止被修改
{
  //size_t = unsigned int
  size_t count = 0;
  while (*s != '\0')
  {
    count++;
    s++;
  }
  return count;
  //char* start = s;
  //while (*s != '\0')//  \0的ASCII编码是0
  //{
  //  s++;
  //}
  //return s - start;
}
int main()
{
  //strlen - 求字符串的长度 - 统计的是\0前面出现的字符的个数
  char arr[] = "abcdef";
  int len = my_strlen(arr);//传的是数组首元素的地址
  //数组名是数组首元素的地址
  printf("%zd\n", len);
  return 0;
}
6.2为什么有传址和传值两种调用方式

因为有一些问题是不使用指针无法解决的!!!

传值调用函数时,函数的实参传给形参,形参是实参的一份拷贝

形参有自己独立的空间,对实参的修改不会影响实参!!!

void Swap1(int a, int b)
{
  a = a ^ b;
  b = a ^ b;  //b = a ^ b ^ b = a ^ 0
  a = a ^ b;
}
void Swap2(int *pa, int *pb)
{
  int t = 0;
  t = *pa;
  *pa = *pb;
  *pb = t;
}
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  //交换a和b的值
  printf("交换前:a = %d b = %d\n", a, b);
  //Swap1(a, b);//传值调用 此处不发生变化
  Swap2(&a, &b);//传址调用
  printf("交换后:a = %d b = %d\n", a, b);
  return 0;
}
int main()
{
  int a = 10;
  int* pa = &a;//a和pa有联系
  *pa = 20;
  printf("%d\n", a);
  return 0;
}
相关文章
|
12天前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
28 3
|
11天前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
22 2
|
20天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
24 1
|
29天前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
29天前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。
|
29天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
C++
魔法指针 之 数组名的理解
魔法指针 之 数组名的理解
12 2
|
1月前
魔法指针 之 二级指针 指针数组
魔法指针 之 二级指针 指针数组
19 1
|
1月前
|
人工智能
魔法指针 之 指针变量的意义 指针运算
魔法指针 之 指针变量的意义 指针运算
18 0
|
1月前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
17 0