【C语言学习笔记】:数组、指针相关面试题
无特殊说明情况下,下面所有题s目都是linux下的32位C程序。
「1、计算以下sizeof的值。」
char str1[] = {'a', 'b', 'c', 'd', 'e'};
char str2[] = "abcde";char *ptr = "abcde";char book[][80]={"计算机应用基础","C语言","C++程序设计","数据结构"};
sizeof(str1)=?
sizeof(str2)=?
sizeof(ptr)=?
sizeof(book)=?
sizeof(book[0])=?
「分析:」
sizeof(str1)=5,就是5*sizeof(char)=5;
sizeof(str2)=6,字符串都是以'\0'结尾,所以所占字节数为6;
sizeof(ptr)=4,ptr是一个指针,在32位平台上大小为4字节;
sizeof(book)=320,book是一个二维数组,4801
sizeof(book[0])=80,book[0]是第一维数组,因为此80*1
根据sizeof求数组元素的个数也很简单,拿第一个来说,就是sizeof(str1)/sizeof(char)。
「2、上面是求计算他们所占字节数,下面来看看怎么求字符串或数组的实际长度。计算下面strlen值。」
char arryA[] = {'a','b','c','\0','d','e'};
char arryB[] = {'a','b','c','d','e'};
char arryC[6] = {'a','b','c','d','e'};
char *str = "abcde";
「分析:」
strlen(arryA) = 3,strlen遇到'\0'就会返回,无论后面有多少个字符;
strlen(arryB)长度无法确定,没有人为写入‘\0’,strlen会继续计算直到找到结束符,结果未知;
strlen(arryC)=5,指定了数组大小,编译器会自动在空余地方添加'\0',这其实跟char arryC[6] = {'a','b','c','d','e','\0'};等价。
strlen(str) = 5,不包括结尾的'\0'。
由以上两个我们来看看strlen和sizeof的区别:
(1)、sizeof是C语言中的一个单目运算操作符,类似++、--等;
用于数据类型,sizeof(type),比如sizeof(int)
用于变量,sizeof(var_name)
注意:sizeof不能用于函数类型、不完全类型或位字段。不完全类型是指具有未知存储大小的数据类型,比如未知存储大小的数组类型、
未知内容的结构体或联合类型,void类型等。例如:sizeof(max),若此时变量max定义为int max(); sizeof(char_v),此时char_v
定义为char char_v[MAX]且MAX未知。
(2)、strlen是个函数,其原型为unsigned int strlen(char *s);
streln的计算必须依赖字符序列中的'\0',通过该字符来判断字符序列是否结束。
「3、忽悠人的char str[]和char *str」
(1)下面的操作合法么?出错的话,会是在那个阶段?编译时期还是运行时期?
char str[] = "hello";
str[0] = 's'; //合法么char *str = "hello";
p[0] = 's'; //合法么
「分析:」
这两个都可以成功编译,只是第二个会在运行时期出现段错误。下面来分析一下:
首先"hello"是一个字符串常量,存储在静态数据区域(data段),这是在编译时期就确定的。第一个是将字符串常量赋值给了一个变量(全局变量在数据段,局部变量在栈区),实际上是将字符串常量拷贝到了变量内存中,因此修改的只是str[]这个变量的值。
第二个是将字符串常量的首地址赋值给p,对p操作就是对字符串常量进行修改!因此出现了段错误。
(2)理解了上面的知识,判断一下下面的true or false?
char str1[] = "abc";
char str2[] = "abc";const char str3[] = "abc";
const char str4[] = "abc";const char *str5 = "abc";
const char *str6 = "abc";char *str7 = "abc";
char *str8 = "abc";cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
「分析:」
结果是:0 0 1 1
先理解str1,str2,str3,str4,他们是什么?他们是数组名,也就是数组首元素的地址!”str1 == str2“本质就是比较两个数组的地址是不是相同。上面我们说过,编译器给他们分配了新的存储空间来对字符串"abc"进行拷贝,这些变量在内存里是相互独立的,因此他们的地址肯定不同!
再理解str5,str6,str7,str8,他们是什么?他们是指针,他们的值就是字符串常量的地址!它们都指向“abc"所在的静态数据区,所以他们都相等。
(3)更深一步:下面程序有问题么?有的话问题出在哪里?如何修改?
#include <stdio.h>
char *returnStr()
{char p[]="hello world!";return p;
}
int main()
{char *str = NULL;str = returnStr();printf("%s\n", str);return 0;
}
「分析:」
p是个局部变量,只是把字符串"hello word!"进行了拷贝,该局部变量是存放在栈中的,当函数退出时,栈被清空,p会被释放,因此返回的是一个已经被释放的内存地址,这样做是错误的。
可以进行如下修改:
#include <stdio.h>
char *returnStr()
{char *p = "hello world!";return p;
}
int main()
{char *str = NULL;str = returnStr();printf("%s\n", str);return 0;
}
搜索公众号C语言中文社区,后台回复“资源”,免费获取200G编程资料。
这么写就不会有问题了,因为"hello world!"存放在静态数据区,将该区的首地址赋值给指针p并返回,即使returnStr函数退出,也不会对字符串常量所在的内存进行回收,因此可以访问该字符串常量。
当然了,也可以这么修改:
#include <stdio.h>
char *returnStr()
{static char p[] = "hello world!";return p;
}
int main()
{char *str = NULL;str = returnStr();printf("%s\n", str);return 0;
}
使用关键字static,static修饰的局部变量也会放在data段,即使returnStr函数退出,也不会收回该内存空间。
「4、数组作为函数参数传递」
我们往往会把数组当做函数的入参,看看下面的函数有啥问题:
int func(int a[]){ int n = sizeof(a)/sizeof(int); for(int i=0;i<n;i++) { printf("%d ",a[i]); a[i]++; }
}
结果却发现n的值总是1!为什么会这样呢?这是因为在C中,将数组传递给一个函数时,无法按值传递,而是会自动退化为指针。下面的三种写法其实是等价的:
"int func(int a[20]);" 等价于 "int func(int a[]);" 等价于 "int func(int *a);"。
「5、两数交换的那些坑」
下面代码想实现两数交换,有什么问题么?
void swap(int* a, int* b)
{ int *p; p = a; a = b; b = p;
}
「分析:」
程序在运行到调用函数时,会将参数压栈,并为之分配新的空间,此时传递进来的其实是一个副本,如下图所示:
image
a的值跟b的值都是地址,交换a和b的值,只是把两个地址交换了而已,也就说只是改变了副本的地址而已,地址所指向的对象并没有改变!。
正确的方法应该是这样的:
void swap(int* a, int* b)
{ int tmp; tmp = *a; *a = *b; *b = tmp;
}
a和b虽然也是副本,但是在函数内部通过该地址直接修改了对象的值,对应的实参就跟着发生了变化。
其实,指针传递和值传递的本质都是值传递,值传递是传递了要传递变量的一个副本。复制完后,实参的地址和形参的地址没有任何联系,对形参地址的修改不会影响到实参,但是对形参地址所指向对象的修改却能直接反映在实参中,这是因为形参所指向的对象就是实参的对象。正因如此,我们在传递指针作为参数时,要用const进行修饰,就是为了防止形参地址被意外修改。
「6、函数参数为指针应小心」
下面的代码有什么问题?运行结果会怎么样?
void GetMem(char *p)
{p = (char*)malloc(100);
}void main()
{char *str = NULL;GetMem(str);strcpy(str, "hello word!");printf(str);
}
「分析:」
程序崩溃。在上面已经分析过了,传递给GetMem函数形参的只是一个副本,修改形参p的地址对实参str丝毫没有影响。所以str还是那个str,仍为NULL,这时将字符串常量拷贝到一个空地址,必然引发程序崩溃。下面的方法可以解决这个问题:
void GetMem(char **p)
{*p = (char*)malloc(100);
}void main()
{char *str = NULL;GetMem(&str);strcpy(str, "hello word!");printf(str); free(str); //不free会引起内存泄漏
}
看似有点晦涩,其实很好理解。本质上是让指针变量str指向新malloc内存的首地址,也就是把该首地址赋值给指针变量str。前面我们说过,指针传递本质上也是值传递,要想在子函数修改str的值,必须要传递指向str的指针,因此子函数要传递的是str的地址,这样通过指针方式修改str的值,将malloc的内存首地址赋值给str。
「7、数组指针的疑惑」
(1)说出下面表达式的含义?
int *p1[10];
int (*p2)[10];
第一个是指针数组,首先他是一个数组,数组的元素都是指针。
第二个是数组指针,首先他是一个指针,它指向一个数组。
下面这张图可以很清楚的说明:
image
(2)写出下面程序运行的结果
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
「分析:」
答案是2,5。本题的关键是理解指针运算,”+1“就是偏移量的问题:一个类型为T的指针移动,是以sizeof(T)为单位移动的。
**a+1:**在数组首元素地址的基础上,偏移一个sizeof(a[0])单位。因此a+1就代表数组第1个元素,为2;
&a+1:在数组首元素的基础上,偏移一个sizeof(a)单位,&a其实就是一个数组指针,类型为int()[5]。因此&a+1实际上是偏移了5个元素的长度,也就是a+5;再看ptr是int类型,因此"ptr-1"就是减去sizeof(int*),即为a[4]=5;
a是数组首地址,也就是a[0]的地址,a+1是数组下一个元素的地址,即a[1];&a是对象的首地址,&a+1是下一个对象的地址,即a[5]。
「8、二级指针疑问」
给定声明 const char * const *pp;下列操作或说明正确的是?
(A)pp++ (B)(*pp)++ (C)(**pp)=\c\; (D)以上都不对
「分析:」
答案是A。
先从「一级指针」说起吧:(1)const char p :限定变量p为只读。这样如p=2这样的赋值操作就是错误的。(2)const char p :p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如p=4这样的操作就错了, 因为企图改写这个已经被限定为只读属性的对象。
(3)char const p :限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而p=3这样的操作合法,因为并没有限定其最终对象为只读。(4)const char const p :两者皆限定为只读,不能改写。再来看「二级指针」问题:(1)const char 「p :p为一个指向指针的指针,const限定其最终对象为只读,显然这最终对象也是为char类型的变量。故像」p=3这样的赋值是错误的, 而像p=?p++这样的操作合法。
(2)const char * const *p :限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的,但是p++这种是合法的。(3)const char * const * const p :全部限定为只读,都不可以改写
「9、*p++、 (*p)++、 *++p、 ++*p」
int a[5]={1, 2, 3, 4, 5};
int *p = a;*p++ 先取指针p指向的值(数组第一个元素1),再将指针p自增1;cout << *p++; // 结果为 1cout <<(*p++); // 1(*p)++ 先去指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2cout << (*p)++; // 1cout <<((*p)++) // 2
*++p 先将指针p自增1(此时指向数组第二个元素),* 操作再取出该值cout << *++p; // 2cout <<(*++p) // 2++*p 先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2)cout <<++*p; // 2 cout <<(++*p) // 2
相关文章:

【C语言学习笔记】:数组、指针相关面试题
无特殊说明情况下,下面所有题s目都是linux下的32位C程序。 「1、计算以下sizeof的值。」 char str1[] {a, b, c, d, e}; char str2[] "abcde";char *ptr "abcde";char book[][80]{"计算机应用基础","C语言","C程…...

go语言环境配置 项目启动
一 安装go语言 go语言各个版本之间兼容性比较差。所以可能你需要安装固定的版本 1 安装最新版的go brew install go2 查看go可以安装的版本 brew search go3 安装指定版本的go brew install go1.134 查看安装的go语言的版本 go version5 查看go的安装路径 which go || w…...

Springboot 使用插件 自动生成Mock单元测试 Squaretest
缘起 很多公司对分支单测覆盖率会有一定的要求,比如 单测覆盖率要达到 60% 或者 80%才可以发布。 有时候工期相对紧张,就优先开发功能,测试功能,然后再去补单元测试。 但是编写单元测试又比较浪费时间,有没有能够很大…...

「JVM 执行引擎」栈架构的字节码的解释执行引擎
JVM 执行引擎在执行 Java 代码时有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择; HotSpot 实际的实现中,模版解释器工作时,并不是按照概念模型中进行机械式计…...

SSM项目-商城后台管理系统
SSM项目-商城后台管理系统开发说明开发环境项目界面演示项目功能具体的技术指标开发过程1、搭建SSM框架1.1、建库建表1.2、新建Maven工程1.3、配置pom.xml1.4、目录结构1.5、jdbc.properties1.6、mybatis-config.xml1.7 两个Spring的配置文件applicationContext_dao.xmlapplica…...

什么是装运单IFTMIN?
符合EDIFACT国际报文标准的IFTMIN主要用于传输电子运输订单,这些装运单作为EDI数据交换的一部分,由客户或托运人发送给物流服务提供商。通过EDI传输的运输信息可以被用来计划当前所需的运输能力,并且物流服务提供商也可以据此提前将包装材料准…...

深度负反馈
负反馈放大电路的方块图因为负反馈放大电路有四种组态,而且对于同一种组态,具体电路也各不相同;所以为了研究负反馈放大电路的共同规律,可以利用方块图来描述所有电路一.负反馈放大电路的方块图表示法任何负反馈放大电路都可以用下…...

【每日随笔】手指训练 ( 产品需求探索、技术无关 | 手指训练作用 | 哪些人需要手指训练 | 手指操 | 手指康复训练器材 )
文章目录一、手指训练作用二、哪些人需要手指训练三、手指操四、手指康复训练器材产品需求探索 , 研究下手指训练的市场 , 前景 , 是否可以开发 ; 一、手指训练作用 手指训练作用 : 改善 上肢协调性手眼 协调性训练提高 手指 抓握 能力提高 手指 灵活性提高 上肢运动 准确性 和…...

Apple Safari 16.3 - macOS 专属免费浏览器 (独立安装包免费下载)
Safari 浏览器 16 for macOS Montery, Big Sur 请访问原文链接:https://sysin.org/blog/apple-safari-16/,查看最新版。原创作品,转载请保留出处。 作者主页:www.sysin.org 之前 Safari 浏览器伴随 macOS 更新一起发布ÿ…...

【java】Spring Boot --Spring Boot 集成 MyBatis
文章目录1. 前言2. 实例场景3. 数据库模块实现4. Spring Boot 后端实现4.1 使用 Spring Initializr 创建项目4.2 引入项目依赖4.3 数据源配置4.4 开发数据对象类4.5 开发数据访问层4.6 添加 MyBatis 映射文件5. 测试6. 小结1. 前言 企业级应用数据持久层框架,最常见…...

python正则表达式
python正则表达式 作者:AOAIYI 创作不易,如果觉得文章不错或能帮到你学习,记得点赞收藏评论一下哦 文章目录python正则表达式一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤总结一、实验目的 学会使用常见的正则表达式 二、…...

【C++】二叉树的非递归遍历
非递归遍历二叉树一、二叉树的前序遍历二、二叉树的中序遍历三、二叉树的后序遍历3.1 方法一3.2 方法二一、二叉树的前序遍历 题目链接 我们可以把任何一棵树看成左路节点,左路节点和右子树。先访问左路节点,再访问左路节点的右子树。在右子树中也重复这…...

Linux——线程同步(条件变量、POSIX信号量)和线程池
一.线程同步(一).概念线程同步是一种多线程关系,指的是线程之间按照特定顺序访问临界资源,进而能够避免线程饥饿问题。所谓线程饥饿指的是某个线程长期“霸占”临界资源,导致其他线程无法访问该资源。而通过线程同步机…...

leaflet 上传CSV文件,导出geojson格式文件(064)
第064个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载CSV文件,将图形显示在地图上。点击导出geojson,下载成geojson文件。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共114行)安装插件…...

Java内部类
文章目录一、内部类的概念二、内部类的分析三、内部类的分类1. 成员内部类2. 静态内部类3. 局部内部类4. 匿名内部类匿名内部类与Lambda表达式一、内部类的概念 在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。内部类…...

Centos系统里运行java的jar包
目前使用springboot开发是嵌入方式的tomcat,不需要单独使用tomcat,那么经常在服务器上运行jar包,这里记录一下在centos7系统里运行jar的方式。在运行之前需要确定centos7系统是否安装了java环境以及配置环境变量,还有jar需要运行的…...

招标采购流程的电子招标采购,是管理复杂供应链和多层供应商的高效方式。
负载均衡(Load Balance) 由于目前现有网络的各个核心部分随着业务量的提高,访问量和数据流量的快速增长,其处理能力和计算强度也相应地增大,使得单一的服务器设备根本无法承担。在此情况下,如果扔掉现有设…...

【C语言】C程序结构和基本语法
1、C语言程序结构 我们学习一门编程语言,第一个实例都是"hello world!",下面看一个最简单的C程序结构。 #include <stdio.h>int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }程序的第一行 #incl…...

YOLOv8 目标检测 | 自定义数据集
本文介绍了使用用于目标检测的自定义数据训练 YOLOv8 模型。我正在使用来自 kaggle 的 yolo 格式的“Face Mask Dataset”,数据集链接如下:https://www.kaggle.com/datasets/maalialharbi/face-mask-dataset?resourcedownloadYOLOv8 是目前最先进的 YOL…...

Lua语法入门
注意:文章将持续更新完善 文章目录一. 初识Lua二. HelloWorld三. Lua的数据类型四. 变量五. 循环六. 函数七. 条件控制一. 初识Lua Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中&#…...

华为OD机试真题JAVA实现【最小步骤数】真题+解题思路+代码(20222023)
🔥系列专栏 华为OD机试(JAVA)真题目录汇总华为OD机试(Python)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出示例一输入输出说明示例二输入输出解题思路...

预检请求OPTIONS
这里写目录标题简单请求和非简单请求简单请求非简单请求预检请求OPTIONS简单请求和非简单请求 浏览器将请求分为两大类:简单请求(simple request)和非简单请求(not-so-simple request) 简单请求 简单请求࿰…...

引入短信服务发送手机验证码进行安全校验
其他方案>引入QQ邮箱发送验证码进行安全校验 相对短信验证码,操作更简单而且免费 最近想给自己的项目在注册时加点安全校验,准备使用免费的邮箱验证来着,在上一篇引入QQ邮箱进行安全校验时,看有朋友说阿里云会送一些短信服务免…...

opencv绘制直线
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...

Seata源码学习(五)- Seata服务端(TC)源码解读
Seata源码分析- Seata服务端(TC)源码解读 上节课我们已经分析到了SQL语句最终的执行器,但是再往下分析之前,我们需要先来分析一下TM客户端与TC端通讯以后,TC端的具体操作 服务端表解释 我们的Seata服务端在应用的时…...

低版本jQuery导致XSS Nuclei FUZZ POC
目录 1.前言 2. Nuclei FUZZ jQuery XSS POC 3.漏洞验证 4.修复建议 1.前言 我记得以前用那些漏扫工具时时常会报一个低版本jQuery的安全问题,当时还不会验证。直到有一天,它托梦给我。我悟了。低版本jQuery导致XSS POC文件文末获取。...

【Linux】进程的描述组织与进程状态
文章目录🎪 进程的描述组织🚀1.什么是进程🚀2.进程的形成🚀3.进程标识符 *⭐3.1 PS命令查看PID⭐3.2 /proc目录查看进程属性🚀4.父子进程⭐4.1 系统调用获取PID⭐4.2 fork创建子进程⭐4.3 fork双返回值问题⭐4.4 写时拷…...

8.2.1.1 WHERE 子句优化
本节讨论可用于处理 WHERE 子句的优化。示例使用 SELECT 语句,但相同的优化适用于 DELETE 和 UPDATE 语句中的 WHERE 子句。 注意 因为 MySQL 优化器的工作正在进行,所以这里并没有记录 MySQL 执行的所有优化。 您可能会尝试重写查询以使算术运算更快&am…...

拆个微波炉,分析一下电路
微波炉是用2450MHz的超高频电磁波来加热食品,它能无损穿越塑料,陶瓷,不能穿越金属,碰到金属会反射,但穿过含水食物,食物内的分子会高速摩擦,产生热量,使食物变熟。在厨房电器中&…...

DM8:DMDSC共享存储集群搭建-共享存储绑定
DM8:DMDSC共享存储集群搭建-共享存储绑定环境介绍:1 发现共享磁盘2 对共享存储进行分区格式化2.1 格式化成功但不可用2.2 解决问题修改错误的分区格式3 配置/etc/rc.d/rc.local3.1 编辑文件(两个节点配置相同)3.2 使rc.local生效4 重启操作系…...