【深入理解C指针】经典笔试题——指针和数组(一)

简介: 【深入理解C指针】经典笔试题——指针和数组

一、指针和数组练习题

首先我们在练习此方面的题目前,回顾一下相关概念。


数组和指针


数组:能够存放一组相同类型的元素,数组的大小取决于数组的元素个数和元素类型。


指针:地址or指针变量,指针的大小为4/8个字节(由CPU的寻址位数决定)


数组是数组,指针是指针,二者不等价。


数组名是数组首元素的地址,这个地址可以存放到指针变量当中。


可以根据指针来遍历数组的操作。


数组名:


大部分情况下表示的是数组首元素的地址。


但是除了以下两个例外:


sizeof(数组名) ,此时的数组名表示的是整个数组,且单独存放在sizeof( )内部,计算的是整个数组大小;


&数组名,此时的数组名表示的是整个数组,取出的就是数组的地址。


sizeof与strlen


1.sizeof计算的是占用内存空间的大小,单位是字节,不会关注内存中到底存放的是什么;


2.sizeof不是函数,而是操作符;


3.strlen是函数;


4.strlen是针对字符串的,求的是字符串的长度,本质上统计的是'\0'之前出现的字符的个数。


1. 一维数组

//一维数组
#include<stdio.h>
int main()
{
    int a[] = {1,2,3,4};
    printf("%d\n",sizeof(a));//1
    printf("%d\n",sizeof(a+0));//2
    printf("%d\n",sizeof(*a));//3
    printf("%d\n",sizeof(a+1));//4
    printf("%d\n",sizeof(a[1]));//5
    printf("%d\n",sizeof(&a));//6
    printf("%d\n",sizeof(*&a));//7
    printf("%d\n",sizeof(&a+1));//8
    printf("%d\n",sizeof(&a[0]));//9
    printf("%d\n",sizeof(&a[0]+1));//10
     return 0;
}

分析:


//1: 数组名单独放在sizeof()内部,计算的是数组总大小,单位是字节。16


//2: 此时的a并没有单独存放在sizeof()内部,a+0计算的就是数组首元素的地址,地址的大小是4/8个字节。


//3:此时的a没有单独存放在sizeof()内部,a表示数组首元素的地址(&a[0]),*a 就等价于*&a[0],等价于a[0],计算的是第一个元素类型的大小,结果是4


//4: a是数组首元素的地址,类型是int*,a + 1就是跳过一个整型的大小,得到的是第二个元素的地址,计算的就是第二个元素地址的大小,结果就是4/8字节


//5:a[1] 等价于 *(a + 1),计算的就是第二个元素的大小,结果是4


//6:&a表示的是取出整个数组的地址,结果计算的就是整个数组地址的大小,结果是4/8个字节


//7:(1)&a表示的是整个数组的地址,*星号解引用,拿到的就是整个数组,  *&a == a,sizeof(a)计算的就是整个数组的大小,结果是16


    (2)也可以看做是&a 放在一个指针中,这个指针的类型就是 int (*) [4],*解引用, 访问的就是一个数组的大小,数组有4个元素,每个元素是int类型,结果就是4 * 4 为16


//8:&a 表示的是整个数组的地址,类型是 int  (*)[4]  ,&a + 1跳过的就是一个数组的大小,


结果是4/8个字节


//9:a[0] 等价于*(a + 0),表示的就是第一个元素,&a[0]就是第一个元素的地址, 结果就是4/8个字节


//10:&a[0] + 1 ,即第一个元素的地址 + 1,得到的是第二个元素的地址,结果是4/8个字节


2. 字符数组

#include<stdio.h>
int main()
{
    char arr[] = {'a','b','c','d','e','f'};
    printf("%d\n", sizeof(arr)); //1
    printf("%d\n", sizeof(arr+0));//2
    printf("%d\n", sizeof(*arr));//3
    printf("%d\n", sizeof(arr[1]));//4
    printf("%d\n", sizeof(&arr));//5
    printf("%d\n", sizeof(&arr+1));//6
    printf("%d\n", sizeof(&arr[0]+1));//7
    printf("%d\n", strlen(arr));//8
    printf("%d\n", strlen(arr+0));//9
    printf("%d\n", strlen(*arr));//10
    printf("%d\n", strlen(arr[1]));//11
    printf("%d\n", strlen(&arr));//12
    printf("%d\n", strlen(&arr+1));//13
    printf("%d\n", strlen(&arr[0]+1));//14
    return 0;
}

 //10 访问时冲突: .image.png

分析:


//1:  arr数组名单独存放在sizeof()内部,计算的是整个数组的大小,故结果是6


//2:  arr + 0 是数组首元素的地址,计算的是地址的大小,结果是4/8个字节。


//3: *arr,表示的是对数组首元素的地址进行解引用,拿到的就是首元素,计算的是数组首元素的大小,结果是1


//4:  arr[1],等价于*(arr + 1),对数组第二个元素的地址解引用,拿到第二个元素,计算的就是第二个元素的大小,结果是1


//5: &arr 表示的就是整个数组的地址,结果是4/8个字节


//6: &arr + 1 表示跳过一个数组的大小,但还是地址,结果还是4/8个字节


//7: &arr[0]表示的就是数组首元素的地址,&arr[0] + 1 跳过一个char类型的大小,结果还是4/8个字节。


//8: 此处的数组名代表的是数组首元素的地址,计算字符串长度,所以需要寻找'\0'的位置,但是我们不确定'\0'会出现在哪个位置,故而我们认为结果是个随机值。


//9: arr + 0,还是表示的是数组首元素的地址,结果同上,依旧是一个随机值。


//10: 这里的arr表示的就是数组首元素的地址,*arr拿到的是'a',但是传入strlen()的参数必须是一个指针,而这里的'a',实际把a的ASCII码值转换为指针的形式,然后访问,但是这个地址并不是“自己”的,所以形成了非法访问的情况。(观察上方图片,0x00000061就是97,即'a'的ASCII码值。)


//11:arr[1]表示的是数组的第二元素,传入strlen,会将'b'(98)当成地址,也会形成非法访问


//12: &arr表示取出整个数组的地址,数组的起始地址仍然指向'a',向后寻找'\0',所以还是一个随机值


//13:&arr + 1跳过了一个数组的大小,仍然向后寻找'\0',直到找到'\0'结束,但它与前一条(//12)相比,会少数6次,故可以认为是随机值 - 6


//14:&arr[0] + 1是第二个元素的地址,即从指向'b'的位置开始向后数,可以认为是随机值 - 1

#inlude<stdio.h>
int main()
{
    char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));//1
    printf("%d\n", sizeof(arr+0));//2
    printf("%d\n", sizeof(*arr));//3
    printf("%d\n", sizeof(arr[1]));//4
    printf("%d\n", sizeof(&arr));//5
    printf("%d\n", sizeof(&arr+1));//6
    printf("%d\n", sizeof(&arr[0]+1));//7
    printf("%d\n", strlen(arr));//8
    printf("%d\n", strlen(arr+0));//9
    printf("%d\n", strlen(*arr));//10
    printf("%d\n", strlen(arr[1]));//11
    printf("%d\n", strlen(&arr));//12
    printf("%d\n", strlen(&arr+1));//13
    printf("%d\n", strlen(&arr[0]+1))//14
    return 0;
}

//12会发生警告

image.png

分析:


//1:数组名单独存放在sizeof()内部,计算的是整个数组的大小,结果是7


//2:  sizeof(arr + 0)计算的是数组首元素地址的大小,结果是4/8个字节


//3:  arr表示的是数组首元素的地址,*arr拿到的是数组首元素,故结果计算的是数组首元素的大小,结果是1


//4: 表示的数组第二个元素的大小,结果是1


//5:  &arr表示的是整个数组地址,计算的是整个数组地址的大小,结果是4/8个字节

//6:  &arr表示数组的地址,+ 1跳过了一个数组的大小,结果是4/8个字节


//7: &arr[0]数组首元素的地址 + 1,跳过一个char类型的大小,表示第二个元素的地址,结果是4/8个字节


//8: arr表示首元素的地址,依次往后寻找,直到找到'\0'为止结束,结果是6


//9:  arr + 0 表示的也是数组首元素的地址,依次往后寻找,直到找到'\0'为止结束,结果为6


//10: *arr表示拿到的是数组首元素,即将'a'传进strlen函数了,会造成非法访问


//11: 也会造成非法访问


//12: &arr拿到的数组的地址,起始地址指向'a', 向后寻找'\0',故结果为6


强调一下:这里的传址发生警告,因为&arr,放到一个指针里面去,它的类型应该是一个数组指针,为char (*)[7],但是strlen()函数的形式参数为const char * 来接收,如上图显示,会显示 “const char *”与“char (*) [7]”的间接级别不同,但是不会影响使用。


//13: &arr + 1 跳过了一个数组的大小,往后寻找'\0',直到找到'\0'为止结束,但这里我们并不知道'\0'的位置,所以结果是随机值


//14: &arr[0] + 1,指向的就是第二个元素的地址,往后寻找'\0',直到找到'\0'为止结束,结果是5

 #inlcude<stdio.h>
int main()
{
    char *p = "abcdef";
    printf("%d\n", sizeof(p));//1
    printf("%d\n", sizeof(p+1));//2
    printf("%d\n", sizeof(*p));//3
    printf("%d\n", sizeof(p[0]));//4
    printf("%d\n", sizeof(&p));//5
    printf("%d\n", sizeof(&p+1));//6
    printf("%d\n", sizeof(&p[0]+1));//7
    printf("%d\n", strlen(p));//8
    printf("%d\n", strlen(p+1));//9
    printf("%d\n", strlen(*p));//10
    printf("%d\n", strlen(p[0]));//11
    printf("%d\n", strlen(&p));//12
    printf("%d\n", strlen(&p+1));//13
    printf("%d\n", strlen(&p[0]+1));//14
   return 0;   
}

image.png

分析:


//1:这里的p是一个指针变量,指向了字符串首元素的地址,即'a'的地址,所以计算它的大小为4/8个字节


//2: p + 1指向了'b'的地址,所以计算它的大小为4/8个字节


//3: *p,即对'a'解引用操作,计算的大小就一个char 类型的大小,结果为1


//4: p[0] 等价于*(p + 0),(可以把字符串想象成一个数组,为内存连续存放的空间),即'a',结果为1


//5: &p,对指针变量p进行取地址操作,是一个一级指针的地址,为一个二级指针,但还是指针,所以大小为4/8个字节


//6: &p + 1,跳过一个char* 类型的数据,结果为4/8个字节


//7: &p[0] + 1,指向了'b'的地址,所以计算它的大小为4/8个字节


//8: 从'a'的地址开始,向后数,直到找到'\0'为止结束,结果为6


//9:  p + 1 指向了'b'的地址,向后数,直到找到'\0'为止结束,结果为5


//10: 把'a'的ascii码值当成了地址,会形成非法访问


//11: p[0]等价于*(p + 0),和*p一样,同样会造成非法访问


//12:&p是从指针变量p向后数,直到找到'\0'为止结束,结果为随机值


//13: &p + 1跳过一个char * 类型的指针,向后数,结果也为一个随机值,但是与第十二条的随机值没有关系。(因为不确定 //12 中指针变量p中的4/8个字节空间中是否存在'\0')


//14: &a[0]为首元素的地址,+ 1跳过一个char 类型的大小,指向了元素'b',所以计算的结果是5


3. 二维数组

#include<stdio.h>
int main()
{
    int a[3][4] = {0};
    printf("%d\n",sizeof(a));//1
    printf("%d\n",sizeof(a[0][0]));//2
    printf("%d\n",sizeof(a[0]));//3
    printf("%d\n",sizeof(a[0]+1));//4
    printf("%d\n",sizeof(*(a[0]+1)));//5
    printf("%d\n",sizeof(a+1));//6
    printf("%d\n",sizeof(*(a+1)));//7
    printf("%d\n",sizeof(&a[0]+1));//8
    printf("%d\n",sizeof(*(&a[0]+1)));//9
    printf("%d\n",sizeof(*a));//10
    printf("%d\n",sizeof(a[3]));//11
    printf("%d\n",sizeof(*a + 1));//12
    return 0;
}

image.png

分析:


//1:  二维数组名单独存放在sizeof内部,计算的是整个二维数组大小,结果是48


//2: a[0][0]即第一行第一个元素,计算的大小是4


//3: a[0]是数组第一行的数组名,这里的数组名单独存放在sizeof()内部,计算的是第一行数组的大小,结果是16


//4: a[0]不是单独存放在sizeof()内部,a[0]表示的是首元素的地址,即第一行第一个元素的地址,相当于&a[0][0],a[0] + 1就是第一行第二个元素的地址,相当于&a[0][1],计算的大小是4/8个字节


//5: *(a[0] + 1)是对第一行第二个元素的地址解引用操作,即a[0][1],计算的大小就是4


//6: a表示二维数组名,为二维数组首元素的地址,二维数组的首元素是第一行,即第一行(第一个一维数组)的地址,类型是int (*) [4],a + 1跳过第一行,指向了第二行(第二个一维数组),即第二行的地址,等价于&a[1],那么计算的结果是4/8个字节


//7:(1)对第二行的地址解引用操作。指向第二行的指针变量的大小是 int * [4],解引用访问了一个数组的大小,数组有4个元素,一个元素占4个字节,计算的总大小是16


    (2)*(a + 1)可以看作是a[1],a[1]是第二行的数组名,单独放在sizeof()内部,计算的是第二行的大小,结果是16

//8: &a[0] 是第一行的地址,&a[0] + 1就是第二行的地址,结果是4/8个字节


//9: &a[0] + 1是第二行的地址,*(&a[0] + 1)计算的就是第二行的大小,等价于sizeof(a[1]),结果就是16


//10: (1) a表示数组首元素的地址,即第一行的地址,*a访问的是第一行,sizeof(*a)计算的就是第一行的大小,结果是16


       (2)*a == *(a + 0)== a[0],将a[0]放在sizeof()内部,计算的也是第一行的大小,结果是16


//11:  这里的a[3]会出现越界访问吗? 结果并不会,因为sizeof这个操作符在程序编译期间就完成了操作,并不会对内部表达式进行计算,所以不会造成越界访问,编译器根据类型属性,就知道了a[3]其实和a[1]、a[2]的大小一样,都是int [4],结果就是16


//12: *a,a表示的是二维数组的数组名,数组名表示的是首元素的地址,即第一行的地址,*a得到的就是第一行,即a[0];a[0] + 1,a[0]是第一行的数组名,表示的是首元素的地址,即第一行第一个元素的地址,可以看作为&a[0][0],a[0] + 1,得到就是第一行第二个元素的地址,计算得到的结果就是4/8个字节


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