【C进阶】第十一篇——指针(一)(字符指针+指针数组+数组指针+指针参数)

简介: 【C进阶】第十一篇——指针(一)(字符指针+指针数组+数组指针+指针参数)

【C初阶】第七篇——指针_接受平凡 努力出众的博客-CSDN博客中我们已经知道:

1.指针就是一个用于存放地址的变量,地址唯一标识一块内存空间.

2.指针的大小是固定的4/8个字节(32位平台/64位平台)。

3.指针是有类型的,指针的类型决定了指针±整数的步长和指针解引用操作时的权限大小

4.指针的运算。

本篇博客中将继续深究探讨指针的进阶内容.

字符指针


在指针的类型中有一种指针类型叫字符指针char*

字符指针的一般使用方法:

#include<stdio.h>
int main()
{
  char ch = 'w';
  char* p = &ch;
  return 0;
}

代码中,将字符变量ch的地址存放在了字符指针p中.

其实,字符指针还有另一种使用方式:

#include<stdio.h>
int main()
{
  const char* p = "hello csdn.";
  printf("%c\n", *p);//打印字符'h'
  printf("%s\n", p);//打印字符串"hello csdn."
  return 0;
}

代码中,字符指针p中存放的并非字符串"hello csdn.".字符指针p中存放的是字符串的首元素地址,即字符'h'的地址


所以,当对字符指针p解引用操作并以字符的形式打印时只能打印字符'h'.我们知道,打印一个字符串只需要提供字符串的首地址即可,既然字符指针p中存放的是字符串的首元素地址,那么我们只要提供P并以字符串的形式打印,便可以打印字符串'


这里有一道题目,可以帮助我们更好的理解字符指针和常量字符串:

#include <stdio.h>
int main()
{
  char str1[] = "hello csdn.";
  char str2[] = "hello csdn.";
  char *str3 = "hello csdn.";
  char *str4 = "hello csdn.";
  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;
}

题目最后打印的结果是:

image.png

题目中str1和str2是两个字符数组,比较str1和str2时,相当于比较数组str1和数组str2的首元素地址,而str1与str2是两个不同的字符数组,创建数组str1和数组str2实惠开辟两块不同的空间,他们的首元素地址当然不同.

image.png

而str3和str4是两个字符指针,他们所指向的都是常量字符串'hello csdn"的首地址,所以str3和str4指向的是同一个地方

image.png

注意:常量字符串与普通字符串的最大区别就是:常量字符串是不可以被修改的字符串,既然不能被修改,那么在内存中没有必要存放两个一模一样的字符串,所以在内存中相同的常量字符串只有一个.

指针数组


我们已经知道了整型数组、字符数组等。整型数组是用于存放整型的数组,字符数组是用于存放字符的数组。

1. int arr1[4];
2. char arr2[5];

数组arr1包含4个元素,每个元素的类型是整型;数组arr2包含5个元素,每个元素的类型是字符型.

指针数组也是数组,是用于存放指针的数组。

int* arr3[5];

数组arr3包含5个元素,每个元素是一个一级整型指针

image.png

以此类推:

1. char* arr4[10];//数组arr4包含10个元素,每个元素是一个一级字符型指针。
2. char** arr5[5];//数组arr5包含5个元素,每个元素是一个二级字符型指针。

数组arr4包含10个元素,每个元素是一个一级字符型指针;数组arr5包含5个元素,每个元素是一个二级字符型指针。

数组指针


数组指针的定义


我们已经知道了,整型指针是指向整型的指针,字符指针是指向字符的指针,那么数组指针应该就是指向数组的指针了.

整型指针和字符指针,在使用时只需取出其整型/字符型的数据的地址,并将地址存入整型/字符型指针即可.

#include<stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;//取出a的地址存入整型指针中
  char ch = 'w';
  char* pc = &ch;//取出ch的地址存入字符型指针中
  return 0;
}

数组指针也是一样,我们只需取出数组的地址,并将其存入数组指针即可。

#include<stdio.h>
int main()
{
  int arr[10] = { 0 };
  int(*p)[10] = &arr;
  //&arr - 数组的地址
  return 0;
}

那么数组指针的指针类型是如何写出来的呢?

首先我们应该知道的是:

1. [ ]的优先级要高于 * 。

2. 一个变量除去了变量名,便是它的变量类型。

比如:

int a = 10;//除去变量名a,变量类型为int
char ch = 'w';//除去变量名ch,变量类型为char
int* p = NULL;//除去变量名p,变量类型为int*

数组也可以这样理解:

int arr[10] = { 0 };//除去变量名arr,变量类型为int [10]
int arr[3][4] = { 0 };//除去变量名arr,变量类型为int [3][4]
int* arr[10] = { 0 };//除去变量名arr,变量类型为int* [10]

数组的变量类型说明了数组的元素个数和每个元素的类型:

image.png

3.一个指针变量除去了变量名和 * ,便是指针指向的内容的类型。

比如:

int a = 10;
int* p = &a;//除去变量名(p)和*,便是P指向的内容(a)的类型->int
char ch = 'w';
char* pc = &ch;//除去变量名(pc)和*,便是pc指向的内容(ch)的类型->char

接下来我们就可以来写数组指针的指针类型了:

#include<stdio.h>
int main()
{
  int arr[10] = { 0 };
  int(*p)[10] = &arr;
  //&arr - 数组的地址
  return 0;
}

首先p是一个指针,所以p必须要先与 * 结合,而[ ]的优先级要高于 * ,所以我们要加上( )以便让p与 * 先结合。

指针p指向的内容,即数组类型是int [10],所以数组指针p就变成了int(*p)[10]。

去掉变量名p后,便是该数组指针的变量类型int( * )[10]。

image.png

如果我们不加( ),那么就变成了int *p[10],因为[ ]的优先级要高于 * ,所以p先与[ ]结合,这时p就变成了一个数组,而数组中每个元素的的类型是int * ,这就是前面说到的指针数组。

image.png

&数组名 VS 数组名


对于一个数组的数组名,它什么时候代表数组首元素的地址,什么时候又代表整个数组的地址,这一直是很多人的疑惑。在这里我给出大家准确的答案:

数组名代表整个数组的地址的情况其实只有两种:

  1. &数组名。
  2. 数组名单独放在sizeof内部,即sizeof(数组名)。

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

比如:

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

对于该数组arr,只有以下两种情况数组名代表整个数组的地址:

1. &arr;
2. sizeof(arr);//arr单独放在sizeof内部

除此之外,所以的arr都代表数组首元素地址,即1的地址。

将其与指针联系起来:

#include<stdio.h>
int main()
{
  int arr[5] = { 1, 2, 3, 4, 5 };
  int* p1 = arr;//数组首元素的地址
  int(*p2)[5] = &arr;//数组的地址
  printf("%p\n", p1);
  printf("%p\n", p2);
  printf("%p\n", p1+1);
  printf("%p\n", p2+1);
  return 0;
}

image.png

因为代码中的arr是数组首元素地址,所以要用int * 的指针接收。而&arr是整个数组的地址,所以要用数组指针进行接收。

虽然一个是数组首元素地址,一个是数组的地址,但是它们存放的都是数组的起始位置的地址,所以将p1和p2以地址的形式打印出来发现它们的值一样。

数组首元素地址和数组的地址的区别在于,数组首元素地址+1只能跳过一个元素指向下一个元素,而数组的地址+1能跳过整个数组指向数组后面的内存空间。

image.png

数组指针的使用


数组指针有一个简单的使用案例,那就是打印二维数组:

#include<stdio.h>
void print(int(*p)[5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)//行数
  {
    int j = 0;
    for (j = 0; j < col; 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;
}

在这里我们打印一个三行五列的二维数组。传参时我们传入二维数组的数组名,明确打印的起始位置;传入行数和列数,明确打印的数据范围。

通过上面对&数组名和数组名的认识,我们知道了这里传入的数组名代表的是二维数组的首元素地址,而二维数组的首元素第一行的元素,即传入的是一维数组的地址,所以我们必须用数组指针进行接收。

打印时,通过表达式 * (*(p+i)+j ) 锁定打印目标:

image.png

数组参数,指针参数


一维数组传参


#include<stdio.h>
void test1(int arr[10])//数组接收
{}
void test1(int *arr)//指针接收
{}
void test2(int *arr[20])//数组接收
{}
void test2(int **arr)//指针接收
{}
int main()
{
  int arr1[10] = { 0 };//整型数组
  int *arr2[20] = { 0 };//整型指针数组
  test1(arr1);
  test2(arr2);
}

整型数组:

当向函数传入整型数组的数组名时,我们有以下几种参数可供接收:

  1. 数组传参数组接收,我们传入的是整型数组,那我们就用整型数组接收。
  2. 传入的数组名本质上是数组首元素地址,所以我们可以用指针接收。数组的元素类型是整型,我们接收整型元素的地址用int * 的指针即可。

整型指针数组

当向函数传入整型指针数组的数组名时,我们有以下几种参数可供接收:


数组传参数组接收,我们传入的是整型指针数组,那我们就用整型指针数组接收。

指针接收,数组的元素是int * 类型的,我们接收int * 类型元素的地址用二级指针int ** 即可。

注意:一维数组传参,函数形参设计时[ ]内的数字可省略。

二维数组传参


#include<stdio.h>
void test(int arr[][5])//数组接收
{}
void test(int(*arr)[5])//指针接收
{}
int main()
{
  int arr[3][5] = { 0 };//二维数组
  test(arr);
}

当向函数传入二维数组的数组名时,我们有以下几种参数可供接收:

  1. 二维数组传参二维数组接收。
  2. 指针接收,二维数组的首元素是二维数组第一行的地址,即一维数组的地址,我们用数组指针接收即可。

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

一级指针传参


#include<stdio.h>
void print(int* p, int sz)//一级指针接收
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(p + i));
  }
}
int main()
{
  int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int* p = arr;//一级指针
  print(p, sz);
  return 0;
}

当我们传入的参数为一级指针时,我们可以用一级指针的形参对其进行接收,那么当函数形参为一级指针的时候,我们可以传入什么样的参数呢?

#include<stdio.h>
void test(int* p)
{}
int main()
{
  int a = 10;
  test(&a);//可以传入变量的地址
  int* p = &a;
  test(p);//可以传入一级指针
  int arr[10] = { 0 };
  test(arr);//可以传入一维数组名
  //...
  return 0;
}

总而言之,只要传入的表达式最终的类型是一级指针类型即可传入。

二级指针传参


#include<stdio.h>
void test(int** p)//二级指针接收
{}
int main()
{
  int a = 10;
  int* pa = &a;
  int** paa = &pa;
  test(paa);//二级指针
  return 0;
}

当我们传入的参数为二级指针时,我们可以用二级指针的形参对其进行接收,那么当函数形参为二级指针的时候,我们可以传入什么样的参数呢?

#include<stdio.h>
void test(int** p)
{}
int main()
{
  int a = 10;
  int* pa = &a;
  test(&pa);//可以传入一级指针的地址
  int** paa = &pa;
  test(paa);//可以传入二级指针
  int* arr[10];
  test(arr);//可以传入一级指针数组的数组名
  //...
  return 0;
}



相关文章
|
1月前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
35 3
|
17天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
21天前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
21天前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
21天前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
25天前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
44 4
|
1月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
40 2
|
2月前
|
存储 C语言 C++
如何通过指针作为函数参数来实现函数的返回多个值
在C语言中,可以通过将指针作为函数参数来实现函数返回多个值。调用函数时,传递变量的地址,函数内部通过修改指针所指向的内存来改变原变量的值,从而实现多值返回。
|
1月前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
39 1
|
2月前
|
存储 搜索推荐 C语言
如何理解指针作为函数参数的输入和输出特性
指针作为函数参数时,可以实现输入和输出的双重功能。通过指针传递变量的地址,函数可以修改外部变量的值,实现输出;同时,指针本身也可以作为输入,传递初始值或状态。这种方式提高了函数的灵活性和效率。