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是一个二维数组,4*80*1
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[] = 's'; //合法么 char *str = "hello";
p[] = '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 ;
}
分析:
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 ;
}
这么写就不会有问题了,因为"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 ;
}
使用关键字static,static修饰的局部变量也会放在data段,即使returnStr函数退出,也不会收回该内存空间。
4、数组作为函数参数传递
我们往往会把数组当做函数的入参,看看下面的函数有啥问题:
int func(int a[])
{
int n = sizeof(a)/sizeof(int);
for(int i=;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;
}
分析:
程序在运行到调用函数时,会将参数压栈,并为之分配新的空间,此时传递进来的其实是一个副本,如下图所示:
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();
} 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();
} 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[1];
int (*p2)[1];
第一个是指针数组,首先他是一个数组,数组的元素都是指针。
第二个是数组指针,首先他是一个指针,它指向一个数组。
下面这张图可以很清楚的说明:
(2)写出下面程序运行的结果
int a[] = { , , , , };
int *ptr = (int *)(&a + );
printf("%d,%d", *(a + ), *(ptr - ));
分析:
答案是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++; // 结果为 1 cout <<(*p++); // 1 (*p)++ 先去指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2
cout << (*p)++; // 1
cout <<((*p)++) // 2
*++p 先将指针p自增1(此时指向数组第二个元素),* 操作再取出该值 cout << *++p; // 2
cout <<(*++p) // 2 ++*p 先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2)
cout <<++*p; // 2
cout <<(++*p) // 2
参考博客:
1、《C语言中sizeof 与strlen 区别 》:http://www.cnblogs.com/kungfupanda/archive/2012/12/24/2831273.html
2、《常量字符串为什么位于静态存储区?》:https://blog.csdn.net/ebw123/article/details/51388340
3、《值传递、指针传递、引用传递的区别》:https://blog.csdn.net/koudan567/article/details/51298511
4、牛客网mlc答案:https://www.nowcoder.com/test/question/done?tid=16211084&qid=1409#summary
C语言面试题目之指针和数组的更多相关文章
- 程序员之---C语言细节12(指针和数组细节,"//"的可移植性说明)
主要内容:指针和数组细节,"//"的可移植性说明 #include <stdio.h> int main(int argc, char **argv) { int a[ ...
- go语言之字符串、指针、数组、切片、结构struct、面向对象
一: 字符串 概述: Go 语言将字符串作为 种原生的基本数据类型,字 符串的初始化可以使用字符串字面量. (1)字符串是常量,可以通过类 数组 索引访问其字节单元,但是不能修改某个字节的值 (2)宇 ...
- C语言程序设计--字符串与指针及数组与指针
数组的基本知识 数组的定义 #define SIZE 5 int array_int[5]; //未声明初始化,默认填零 float array_float[5] = {1.01, 2.23, 3.1 ...
- 面试题目——《CC150》数组与字符串
面试题1.1:实现一个算法,确定一个字符串的所有字符是否全都不同.假使不允许使用额外的数据结构,又该如何处理? 注意:ASCII字符共有255个,其中0-127的字符有字符表 第一种解法:是<C ...
- 深入理解C语言中的指针与数组之指针篇
转载于http://blog.csdn.net/hinyunsin/article/details/6662851 前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...
- 深入理解C语言中的指针与数组之指针篇(转载)
前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本所在.相信,任意一家公司如果想要考察一个人对C语言的理解,指针和数组绝对是必考的一部分. 但是之前一方面之前一直在忙各种事情 ...
- C语言经典面试题目(转的,不过写的的确好!)
第一部分:基本概念及其它问答题 1.关键字static的作用是什么? 这个简单的问题很少有人能回答完全.在C语言中,关键字static有三个明显的作用: 1). 在函数体,一个被声明为静态的变量在这一 ...
- 指针与数组的区别 —— 《C语言深度剖析》读书心得
原书很多已经写的很清楚很精炼了,我也无谓做无意义的搬运,仅把一些基础和一些我自己以前容易搞混的地方写一下. 1. 意义: 指针: 指针也是一种类型,长度为4字节,其存放的内容只能是一个地址(4字节). ...
- C语言指针与数组的定义与声明易错分析
部分摘自<C语言深度解剖> 1.定义为数组,声明为指针 在文件1中定义: char a[100]; 在文件2中声明: extern char *a; //这样是错误的 这里的extern告 ...
随机推荐
- Jenkins+maven+gitlab自动化部署之前端构建发布(六)
前端项目构建,需要在jenkins主机部署node服务,网上有说介绍说安装对应的nodejs插件进行前端项目构建,我这里是直接调用系统npm命令,进行前端打包.具体node部署参考:Centos7部署 ...
- 《Mysql - Mysql 是如何保证主备一致的?》
一:Mysql 主备的基本原理? - 主备切换流程(M-S 架构) - - 在状态 1 中,客户端的读写都直接访问节点 A,而节点 B 是 A 的备库,只是将 A 的更新都同步过来,到本地执行. - ...
- Java中XML的四种解析方式(一)
XML是一种通用的数据交换格式,它的平台无关性.语言无关性.系统无关性给数据集成与交互带来了极大的方便.XML在不同的语言环境中解析的方式都是一样的,只不过实现的语法不同而已. XML文档以层级标签的 ...
- 用selenium控制已打开的浏览器
在使用selenium进行自动化测试会遇到,手工打开浏览器,做了一部分操作后,并打开相关页面后再执行相关的自动化脚本. 如何使用selenium来接管先前已打开的浏览器呢?醍提出一个Google Ch ...
- WUSTOJ 1325: Distance(Java)坐标计算
题目链接:1325: Distance Description There is a battle field. It is a square with the side length 100 mil ...
- C++标识符的作用域与可见性
一.标识符的作用域与可见性 作用域讨论的是标识符的有效范围,可见性讨论的是标识符是否可以被引用. 二.作用域 作用域是一个标识符在程序正文中有效的区域.C++中标识符的作用域有函数原型作用域.局部作用 ...
- PB笔记之导入、导出组件
导入组件 导出组件
- 数据库去空格 去table 去回车符号 去重
1 update bd_prod_cate c set c.cate_name = replace(c.cate_name,chr(9),'')//去掉tab符号的 2 update bd_prod_ ...
- 原生 js 录屏功能
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...
- io.ByteIO和open操作二进制流的区别(转)
转自Stack Overflow:https://stackoverflow.com/questions/42800250/difference-between-open-and-io-bytesio ...