C语言 面试题,C语言面试题目
主页:安全可靠
作者:博客后起之秀2021年Top2
我们的口号:一点点,大梦想
作者要求:由于博主水平有限,难免有错误和失实之处。我也非常渴望知道这些错误。求铁汁批评指正。
热点栏目:蓝桥杯基础算法分析
首先复习一个知识点:
传统上,数组名表示第一个元素的地址。
有两个例外:
Zeof(数组名):sizeof中放入单个数组名,此时的数组名不再代表第一个元素的地址,而是整个数组。计算整个数组的大小,单位是字节(注意这个单个的名字!);数组名:获取整个数组的地址。好了,介绍完这个知识点,请看下面这一系列的钢笔试题:
一.一维数组//一维数组
#包含stdio.h
int main()
{
int a[]={ 1,2,3,4 };
printf(%d\n ,sizeof(a));//16
//将单个数组名A放入sizeof,此时的数组名代表整个数组,计算整个数组的大小(注意这个单个名称!)
printf(%d\n ,sizeof(a 0));//4/8-32位平台上为4字节,64位平台上为8字节
//此时sizeof中放置了多个数组名A,所以A表示第一个元素的地址,类型为int*,a 0跳过0个整数表示第一个元素的地址。
//地址就是指针,32位平台占用4个字节,64位平台占用8个字节。
printf(%d\n ,sizeof(* a));//4
//此时,A表示第一个元素的地址。如果取消对它的引用,就会得到第一个元素,所以这里会计算第一个元素的大小。
printf(%d\n ,sizeof(a 1));//4/8
//此时,A表示第一个元素的地址,类型为int*,a 1跳过一个整数,所以a 1表示第二个元素的地址,也就是2的地址。
printf(%d\n ,sizeof(a[1]));//4
//计算第二个元素的大小
printf(%d\n ,sizeof(a));//4/8
//a取整个数组的地址,类型为int(*)[4],属于数组指针,数组指针还是指针。
printf(%d\n ,sizeof(* a));//16
//理解1:可以认为*和运算抵消了,此时只剩下sizeof(a),a)。a表示整个数组,计算整个数组的大小;
//理解2: A,取出整个数组的地址,其类型为int(*)[4]数组指针类型,然后解引用。此时,整个数组被访问,其大小为16字节。
printf(%d\n ,sizeof(a 1));//4/8
//a取出整个数组的地址,类型为int(*)[4]数组指针。1跳过整个数组的地址,但地址是4/8字节。
printf(%d\n ,sizeof(a[0]));//4/8
//a[0],取出第一个元素的地址。
printf(%d\n ,sizeof(a[0]1));//4/8
//a[0] 1,跳过1个整数,所以a[0] 1访问第二个元素的地址。
返回0;
}
第二,字符数组
1.问题1注意这样一个概念:
尺寸:
Zeof只关注占用空间的大小;Sizeof不讲究类型;Sizeof是运算符
strlen():
Strlen()关注字符串中“\0”之前的字符数;Strlen()仅用于字符串;Strlen()是一个库函数。好的,请看下面的代码:
代码1:
#包含stdio.h
int main()
{
char arr[]={ a , b , c , d , e , f };
printf(%d\n ,sizeof(arr));//6
//计算整个数组的大小
printf(%d\n ,sizeof(arr 0));//4/8
//计算第一个元素的地址
printf(%d\n ,sizeof(* arr));//1
//计算第一个元素的大小
printf(%d\n ,sizeof(arr[1]));//1
//计算第二个字符的大小
printf(%d\n ,sizeof(arr));//4/8
//arr,取出整个数组的地址,地址就是指针。
printf(%d\n ,sizeof(arr 1));//4/8
//arr 1,跳过了整个数组,但还是地址。
printf(%d\n ,sizeof(arr[0]1));//4/8
//arr[0]表示第一个字符的地址,类型是char*,所以arr[0] 1跳过一个字符,指向第二个字符的地址。
返回0;
}代码2:
#包含stdio.h
int main()
{
char arr[]={ a , b , c , d , e , f };
printf(%d\n ,strlen(arr));//随机值
//strlen()遇到“\0”的结尾。计算了 \0 前的字符数,但arr中没有 \0 ,所以不知道它在哪里结束,所以这里是随机值。
printf(%d\n ,strlen(arr 0));//随机值
//同上
printf(%d\n ,strlen(* arr));//内存访问冲突
//strlen()中的参数是指针类型(可以认为其中存储了地址),*arr表示第一个元素,即‘a’,其ACSII值为97,
//所以strlen(*arr)将 97 作为内存地址访问,会导致内存访问冲突。
printf(%d\n ,strlen(arr[1]));//内存访问冲突
//同上,arr[1]表示 b ,ACSII值为98,strlen(arr[1])表示 98 作为内存地址访问。
printf(%d\n ,strlen(arr));//随机值
//arr,取出整个数组的地址。因为数组里没有 \0 ,所以不知道会在哪里结束。
printf(%d\n ,strlen(arr 1));//随机值
printf(%d\n ,strlen(arr[0]1));//随机值
返回0;
}
这是这行代码的另一个重点:
printf(%d\n ,strlen(* arr));
stren()需要的是一个地址,从这个地址开始向后搜索字符,直到 \0 。计算 \0 前的字符数,但*arr表示第一个元素,即 a 。此时,ACSII值“a”被传递给strlen(),strlen()将从97开始。
2.问题2代码1:
#包含stdio.h
int main()
{
char arr[]= abcdef ;
printf(%d\n ,sizeof(arr));//7
//注意sizeof(arr)计算arr数组占用的内存空间,包括 \0
printf(%d\n ,sizeof(arr 0));//4/8
//比较简单,就不赘述了。
printf(%d\n ,sizeof(* arr));//1
printf(%d\n ,sizeof(arr[1]));//1
printf(%d\n ,sizeof(arr));//4/8
printf(%d\n ,sizeof(arr 1));//4/8
printf(%d\n ,sizeof(arr[0]1));//4/8
返回0;
}代码2:
有了上面的经验,那么下面的对你来说一定不难!
#包含stdio.h
int main()
{
char arr[]= abcdef ;
printf(%d\n ,strlen(arr));//6
printf(%d\n ,strlen(arr 0));//6
printf(%d\n ,strlen(* arr));//内存访问冲突
printf(%d\n ,strlen(arr[1]));//内存访问冲突
printf(%d\n ,strlen(arr));//6(这里要特别注意)
printf(%d\n ,strlen(arr 1));//随机值
printf(%d\n ,strlen(arr[0]1));//5
返回0;
}
3.问题3代码1:
#包含stdio.h
int main()
{
char * p= abcdef
printf(%d\n ,sizeof(p));//4/8
printf(%d\n ,sizeof(p 1));//4/8
printf(%d\n ,sizeof(* p));//1
printf(%d\n ,sizeof(p[0]));//1
printf(%d\n ,sizeof(p));//4/8
printf(%d\n ,sizeof(p 1));//4/8
printf(%d\n ,sizeof(p[0]1));//4/8
返回0;
}代码2:
#包含stdio.h
int main()
{
char * p= abcdef
printf(%d\n ,strlen(p));//6
printf(%d\n ,strlen(p 1));//5
printf(%d\n ,strlen(* p));//内存访问冲突
printf(%d\n ,strlen(p[0]));//内存访问冲突
printf(%d\n ,strlen(p));//随机值(注意哦,这是特别容易出错的地方,特别容易错写成6)
//这里我想说的是,P指的是常量字符串,和字符数组不一样。p有自己的空间。
printf(%d\n ,strlen(p 1));//随机值
printf(%d\n ,strlen(p[0]1));//5
返回0;
}
补充常量字符串在指针类型中,我们知道有一种指针类型是char*,它有两种用法:
一般用途:
#包含stdio.h
int main()
{
char ch= w
char * pc=ch
* pc= a
printf(%c\n ,ch);
返回0;
}这个用法比较简单,我就不多赘述了。看一下下面的用法:
#包含stdio.h
int main()
{
char * p= abcdef
printf(%s\n ,p);
返回0;
}
注意,上面的abcdef是一个常量字符串。内存中存储的只读数据区(只读不可写)特别容易让我们以为字符串abcdef放在字符指针P中,其实本质上就是字符串abcdef在P中第一个字符的地址。
所以下面的代码是有问题的:
#包含stdio.h
int main()
{
char * p= abcdef
* p= w//目的是把‘a’改成‘w’(bug)
返回0;
}上面第6行的代码是错误的,因为常量字符串不能修改,所以为了避免上面的错误,可以将第5行的代码修改为:
const char * p= abcdef这样就可以避免上面的错误。
面试问题:
#包含stdio.h
int main()
{
char str1[]=hello bit ;
char str2[]=hello bit ;
char* str3=hello bit ;
char* str4=hello bit ;
if (str1==str2)
printf(str1和str2相同\ n );
其他
printf(str1和str2不相同\ n );
if (str3==str4)
printf(str3和str4相同\ n );
其他
printf(str3和str4不相同\ n );
返回0;
}
思考解决问题:
Str1和str2是两个字符数组。数组的操作方式是把右边的常量字符串复制到数组的空间里,所以是两个空格,但是内容是一样的。作为数组名,str1和str2是数组第一个元素的地址,所以str1!=str2str3和str4是指向同一个常量字符串的两个字符指针,而常量字符串存储在单独的内存区(只读数据区)。当几个指针指向同一个常量字符串时,实际上是指向同一个内存块。
三。二维数组# includesdio.h
int main()
{
int arr[3][4]={ 0 };
printf(%d\n ,sizeof(arr));//12*4=48
//sizeof(数组名),以字节为单位计算整个数组的大小。
printf(%d\n ,sizeof(arr[0][0]));//4
printf(%d\n ,sizeof(arr[0]));//4*4=16
//arr[0]相当于第一行的数组名。由于是单独放在sizeof里面,所以计算第一行的数组大小,单位是字节。
printf(%d\n ,sizeof(arr[0]1));//4/8
//arr[0]相当于第一行的数组名。由于arr[0]不是单独放在sizeof内部,所以此时它表示第一行的第一个元素的地址。
printf(%d\n ,sizeof(*(arr[0]1)));//4
//前面提到的arr[0]表示第一行第一个元素的地址,所以*(arr[0] 1)表示arr[0][1]元素本身
printf(%d\n ,sizeof(arr 1));//4/8
//arr数组名表示第一个元素的地址,也就是第一行数组的地址,所以arr 1表示第二行数组的地址。
printf(%d\n ,sizeof(*(arr 1)));//4*4=16
//前面提到的arr 1代表第二行数组的地址,对其进行解引用。被访问的第二行数组放在sizeof内部,计算第二行数组的大小。
printf(%d\n ,sizeof(arr[0]1));//4/8
//arr[0]表示第一行数组的地址,那么arr 1表示第二行数组的地址,地址就是指针。
printf(%d\n ,sizeof(*(arr[0]1)));//4*4=16
//前面提到arr[0] 1表示第二行数组的地址,所以解引用它表示第二行数组,单独放入sizeof,计算第二行数组的大小。
printf(%d\n ,sizeof(* arr));//4*4=16
//arr表示第一个元素的地址,即数组第一行的地址。它的解引用表示第一行数组,分别放在sizeof中,计算第一行数组的大小。
printf(%d\n ,sizeof(arr[3]));//4*4=16
//这里我们可能会认为arr[3]越界了,但是对这个问题没有影响。sizeof会推导出arr[3]的类型是int[4]
返回0;
}
第四,模拟字符串库函数。
1.strlen()函数原型:
功能函数:
求绳子的长度
注意:返回值类型是size_t,这个size_t到底是什么?
其实size_t是为sizeof运算符的返回值设计的。可以简单的认为size_t就是众所周知的无符号int。因为是求长度,所以肯定不会是负数,所以用的是无符号整数,但是也容易出现bug。请看下面的代码:
解释下面的代码。输出结果是什么?
#包含stdio.h
#包含字符串. h
int main()
{
if(strlen( ABC )-strlen( abcdef )0)
printf(“”);
其他
printf(=);
返回0;
}是的,答案是输出" "。为什么?3-6=-3鸭子,怎么回事?
这是因为strlen的返回值是size_t,属于无符号数,所以两个无符号数相减,答案一定是无符号数。
那怎么修改呢?
方案1:强制类型转换
#包含stdio.h
#包含字符串. h
int main()
{
if((int)strlen( ABC )-(int)strlen( abcdef )0)
printf(“”);
其他
printf(=);
返回0;
}方案二:直接比较
#包含stdio.h
#包含字符串. h
int main()
{
if (strlen(abc) strlen(abcdef ))
printf(“”);
其他
printf(=);
返回0;
}注意:
以字符串 \0 作为结束标记,strlen返回字符串中 \ 0 之前的字符数(不包括 \ 0 );参数中指向的字符串必须以“\ 0”结尾;注意strlen函数的返回值是size_t类型,属于无符号类型(特别容易出错)。
代码示例:
#包含stdio.h
#包含字符串
int main()
{
int len=strlen( abcdef );
printf(%d\n ,len);
返回0;
}自定义函数模拟实现strlen():
方法:
计数器递归方法指针-指针法
方法1:计数器方法
int my_strlen(const char* str)
{
断言(str);//断言字符串不为空
int count=0;
while (*str!=\0)
{
数数;
str
}
返回计数;
}方法二:递归法
int my_strlen(const char* str)
{
断言(str);//断言字符串不为空
//找到边界
if (*str==\0 )
{
返回0;
}
int count=my _ strlen(str 1);
//注意str和str 1是不同的概念
数数;
返回计数;
}
这里需要注意的是,str和str 1不是同一个概念。先用str,这个题目可以写成str的形式,可以先用。
方法3:指针-指针
指针指针实际上表示两个指针中间的元素数量。注意中间没有几个字节。
int my_strlen(const char* str)
{
断言(str);
const char * cur=str
while (*cur!=\0)
{
cur
}
返回cur-str;
}
2.2.strcpy()函数的原型:
功能函数:
将源字符串复制到目标字符串,并返回目标字符串第一个元素的地址。
解释以下代码:
#包含stdio.h
#包含字符串. h
int main()
{
char arr 1[]= abcdef ;
char arr 2[20]={ 0 };
printf(%s\n ,strcpy(arr2,arr 1));
返回0;
}本体是将字符串arr2复制到字符串arr1,返回ARR2第一个元素的地址(目标字符串第一个元素的地址)。所以上面代码的输出是:abcdef,所以这里的问题是,字符串arr1中的 \ 0 会被复制到字符串arr2中吗,所以这里用下面的代码来验证这个问题:
#包含stdio.h
#包含字符串. h
int main()
{
char arr 1[]= abcdef ;
char arr 2[20]= XXXXXXXXXX ;
printf(%s\n ,strcpy(arr2,arr 1));
返回0;
}首先,当字符串arr1还没有复制到arr2时,它们存储的数据如下:
执行strcpy(arr2,arr1)时,此时存储在arr2中的数据如下:
可以看到,将字符串arr1(源字符串)复制到字符串arr2(目标字符串)会自动复制源字符串末尾的 \ 0 ,并返回目标字符串起始位置的地址。
这里还有一个问题:如果源字符串末尾没有加 \ 0 (这里以字符数组的形式演示,正常字符串的末尾会自动填充 \ 0 ),请看下面的代码:
#包含stdio.h
#包含字符串. h
int main()
{
char arr1[]={ a , b , c , d , e , f };
char arr 2[20]= XXXXXXXXXX ;
printf(%s\n ,strcpy(arr2,arr 1));
返回0;
}代码执行结果:
所以需要注意的是,如果要将源字符串复制到目标字符串,必须确保源字符串包含 \ 0 ,否则复制会失败。
因此,在使用strcpy复制字符串时,需要注意以下几点:
源字符串必须以“\ 0”结尾;复制时,源字符串中的“0”将被复制到目标字符串中;目标字符串(目标空间)必须足够大,以确保源字符串可以被存储;还有一点就是目标字符串必须是可变的。
请看下面的代码:
#包含stdio.h
#包含字符串. h
int main()
{
char arr 1[]= abcdef ;
const char * p= XXXXXXXXXX
printf(%s\n ,strcpy(p,arr 1));
返回0;
}
执行程序是错误的,因为此时的目标字符串是由const修饰的,不可修改。
自定义功能模拟实现strcpy()
代码执行:
char* my_strcpy(char* dest,const char* src)
{
char * ret=dest
断言(dest src);
While (*dest=*src )//太棒了
{
;
}
返回ret
}
3.3.strcat()函数的原型:
功能函数:
将源字符串追加到目标字符串的末尾,并返回目标字符串第一个字符的地址。
解释以下代码:
#包含stdio.h
#包含字符串. h
int main()
{
char arr 1[30]= hello ;
char arr 2[]= world ;
strcat(arr1,arr 2);
printf(%s\n ,arr 1);
返回0;
}上面的代码是将“world”拼接到“hello”的后面,所以打印出来的结果如下:
其实使用strcat库函数的注意事项和strcpy很像,这里就不重复解释了:
源字符串必须以“\ 0”结尾;目标空间必须足够大,以容纳源字符串的内容;目标必须是可变的。
所以想象一下:如果让我们自己实现strcat函数,我们该怎么办?先想想大概意思。首先,我们需要找到目标字符串的结束标记 \0 ,然后将源字符串拼接到目标字符串的后面,最后返回目标字符串第一个字符的地址。这似乎很简单,所以让我们来实现它:
通过自定义函数模拟实现strcat()
代码执行:
char* my_strcat(char* dest,const char* src)
{
char * ret=dest
断言(dest src);
//1,找到目标字符串的\0
While (*dest)//注意,把*dest写成循环条件是错误的,因为\0会被跳过。请小心。
{
dest
}
//2.复制源字符串
while (*dest=*src)
{
;
}
返回ret
}
4.4.strcmp()函数的原型:
功能函数:
比较相应位置字符的字典顺序。
标准:
第一个字符串和第二个字符串,则返回一个大于0的数字;第一个字符串==第二个字符串,则返回0;第一个字符串和第二个字符串,则返回小于0的数字。
敲黑板:
两个字符串不能直接比较或者加减,因为字符串代表第一个字符的地址。也就是说,如果直接进行比较,比较的不是字符串的内容,而是地址,所以是错误的。
通过自定义函数模拟实现strcmp()
代码执行:
int my_strcmp(const char* str1,const char* str2)
{
断言(str 1 str 2);
While (*str1==*str2)//注意是循环体中判断相等的情况。想想为什么。
{
if (*str1==\0 )
返回0;
str1
str2
}
if(* str 1 * str 2)//return * str 1-* str 2;
返回1;
其他
return-1;
}
5.模拟记忆功能。
1.memcpy()函数原型:
注意,内存操作函数中的第三个参数单位是字节。
为什么是void*型?
因为设计者在设计的时候不知道这个库函数会用什么类型来复制,所以最好设计成void*。
请看下面的代码:
#包含stdio.h
#包含字符串. h
int main()
{
int arr1[]={ 1,2,3,4,5,6,7,8,9,10 };
int arr 2[5]={ 0 };
memcpy(arr2,arr1 5,5 * sizeof(arr 1[0]);
int I=0;
for(I=0;i5;我)
{
printf(%d ,arr 2[I]);
}
返回0;
}
自定义功能模拟实现memcpy()
代码执行:
void* my_memcpy(void* dest,const void* src,size_t num)
{
void * ret=dest//注意返回类型是void*,不是void,
断言(dest src);
while(num-)//先用它,然后-
{
*(char *)dest=*(char *)src;//想想为什么把类型强制转换成char*,因为只有它最合适
dest=(char *)dest 1;//注意直接dest,src是错误的,因为是空类型。
src=(char *)src 1;
}
返回ret
}
2.memmove()函数原型:
实际上,C语言只要求:
Memy可以复制不重叠的内存空间,memmove处理那些重叠的内存副本。也就是说,如果memcpy的函数是A,那么memmove的函数就是A B。
那么什么是重叠内存副本呢?请看下面的代码:
#包含stdio.h
#包含字符串. h
int main()
{
int arr1[]={ 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 2,arr1,5 * sizeof(arr 1[0]);//当处理相同的空间时,会出现重叠的内存副本
int I=0;
for(I=0;i 10我)
{
printf(%d ,arr 1[I]);
}
返回0;
}自定义功能模拟实现memmove
代码执行:
void* my_memmove(void* dest,const void* src,size_t num)
{
void * ret=dest
断言(dest src);
If (dest src)//从前到后复制
{
while (num -)
{
*(char *)dest=*(char *)src;
dest=(char *)dest 1;
src=(char *)src 1;
}
}
Else//从后向前复制
{
//src=(char *)src num-1;//一定要-1
//dest=(char *)dest num-1;
//while (num -)
//{
//*(char *)dest=*(char *)src;
//dest=(char *)dest-1;
//src=(char *)src-1;
//}
while(num-)//首先使用它,然后-,在循环中,它是num在-(真奇妙)之后
{
*((char*)目标编号)=*(char *)src编号);
}
}
返回ret
}
六、遇见安然遇见你,不辜负守则。
今天的面试问题到此为止。有所收获的老铁,正愁用小手搞三网融合。
安然无恙。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。