C语言面试高频(指针)
指针
1 说说数组和指针的区别⭐⭐⭐⭐⭐
数组和指针在 C 和 C++ 等编程语言中是两个不同但又紧密相关的概念,以下从多个方面介绍它们的区别:
定义和本质
- 数组:是一种复合数据类型,它是一组相同类型元素的集合,在内存中占据一段连续的存储区域。数组在定义时就确定了大小,这个大小在编译时就已经确定,后续不能再改变。例如
int arr[5];
定义了一个包含 5 个int
类型元素的数组。 - 指针:是一种变量,其存储的是内存地址。指针可以指向任意类型的数据,包括数组、单个变量、函数等。指针的大小通常取决于操作系统的位数,例如在 32 位系统中指针大小为 4 字节,在 64 位系统中为 8 字节。例如
int *ptr;
定义了一个指向int
类型数据的指针。
内存分配
- 数组:数组的内存分配有两种情况。静态数组(如在函数外部或函数内部用
static
关键字修饰的数组)在程序的全局数据区或栈上分配内存;自动数组(在函数内部定义且没有static
修饰)在栈上分配内存。数组的内存分配和释放由编译器自动处理,程序员无需手动干预。 - 指针:指针本身的内存分配通常在栈上,但指针所指向的内存可以通过不同方式分配。如果指针指向静态变量或自动变量,那么这些变量的内存分配和数组类似;如果指针用于动态内存分配(如使用
malloc
、calloc
、realloc
或new
),则内存分配在堆上,需要程序员手动释放。
访问方式
- 数组:数组元素可以通过下标直接访问,下标从 0 开始。例如
arr[2]
表示访问数组arr
的第 3 个元素。这种访问方式直观且简单,编译器会根据数组的起始地址和元素的类型计算出要访问元素的实际地址。 - 指针:指针访问数据需要通过解引用操作符
*
或使用指针算术运算。例如*ptr
表示访问指针ptr
所指向的内存中的值;ptr + 2
表示将指针向后移动 2 个元素的位置(移动的字节数取决于指针所指向的数据类型)。指针的访问方式更加灵活,但也更容易出错。
大小计算
- 数组:使用
sizeof
运算符可以得到整个数组所占用的内存大小,即数组元素个数乘以每个元素的大小。例如sizeof(arr)
可以得到数组arr
的总字节数。通过sizeof(arr) / sizeof(arr[0])
可以计算出数组的元素个数。 - 指针:
sizeof
运算符作用于指针时,得到的是指针本身的大小,而不是指针所指向的内存块的大小。例如sizeof(ptr)
得到的是指针变量ptr
占用的字节数,与它所指向的数据无关。
作为函数参数
- 数组:当数组作为函数参数传递时,实际上传递的是数组首元素的地址,也就是一个指针。在函数内部,无法通过
sizeof
运算符得到数组的真实大小,因为此时数组已经退化为指针。 - 指针:指针作为函数参数传递时,传递的也是指针本身的值(即内存地址)。函数可以通过这个指针修改指针所指向的内存中的值。
可变性
- 数组:数组名代表数组的起始地址,它是一个常量指针,不能被赋值。例如
arr = ptr;
这样的赋值操作是不合法的。 - 指针:指针是一个变量,可以被赋值为不同的地址。例如
ptr = &arr[0];
或ptr = (int *)malloc(sizeof(int) * 5);
都是合法的操作。
2 数组指针与指针数组的区别⭐⭐⭐⭐⭐
数组指针和指针数组是 C 和 C++ 等编程语言里容易混淆的两个概念,下面从定义、语法、内存布局和使用场景等方面来介绍它们的区别。
定义和语法
- 数组指针:本质是一个指针,这个指针指向一个数组。声明时需要指定指针所指向数组的元素类型和数组大小。语法格式为
类型 (*指针名)[数组大小]
。例如int (*p)[5];
,这里p
就是一个指向包含 5 个int
类型元素数组的指针。 - 指针数组:本质是一个数组,数组中的每个元素都是指针。声明时要指定数组的大小和指针所指向数据的类型。语法格式为
类型 *数组名[数组大小]
。例如int *p[5];
,表示p
是一个包含 5 个int
类型指针的数组。
内存布局
- 数组指针:数组指针只占用一个指针大小的内存空间,用来存储它所指向数组的首地址。例如在 32 位系统中,数组指针通常占 4 字节,在 64 位系统中占 8 字节。它指向的是一个连续的数组内存块。
- 指针数组:指针数组是一个数组,会占用多个指针大小的内存空间,具体大小取决于数组的元素个数。例如
int *p[5];
在 32 位系统中会占用 20 字节(5 个指针,每个 4 字节)。数组中的每个元素(指针)可以指向不同的内存地址,这些地址可以是分散的。
使用场景
- 数组指针:常用于二维数组的处理,尤其是当二维数组作为函数参数传递时,使用数组指针可以方便地对二维数组进行操作。例如:
#include <stdio.h> void printArray(int (*p)[3], int rows) { for (int i = 0; i < rows; i++) { for (int j = 0; j < 3; j++) { printf("%d ", p[i][j]); } printf("\n"); } } int main() { int arr[2][3] = {{1, 2, 3}, {4, 5, 6}}; printArray(arr, 2); return 0; }
在这个例子中,printArray
函数的参数p
就是一个数组指针,它指向一个包含 3 个int
类型元素的数组。
- 指针数组:常用于处理多个字符串或者多个不同的对象。例如,当需要存储多个字符串时,可以使用指针数组来存储每个字符串的首地址:
#include <stdio.h> int main() { char *strArray[3] = {"apple", "banana", "cherry"}; for (int i = 0; i < 3; i++) { printf("%s\n", strArray[i]); } return 0; }
在这个例子中,strArray
是一个指针数组,每个元素都是一个指向char
类型(字符串)的指针。
初始化和赋值
- 数组指针:初始化时需要将其指向一个数组的首地址。例如
int arr[5] = {1, 2, 3, 4, 5}; i
,这里指向了数组。
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
嵌入式/C++面试八股文 文章被收录于专栏
该专栏面向嵌入式开发工程师、C++开发工程师,包括C语言、C++,操作系统,ARM架构、RTOS、Linux基础、Linux驱动、Linux系统移植、计算机网络、数据结构与算法、数电基础、模电基础、5篇面试题目、HR面试常见问题汇总和嵌入式面试简历模板等文章。超全的嵌入式软件工程师面试题目和高频知识点总结! 另外,专栏分为两个部分,大家可以各取所好,为了有更好的阅读体验,后面会持续更新!!!