这篇文章是对linux 近三年的纳新面试题的小小总结,篇幅有点长,希望你可以耐心看完,看完你一定有收获,有什么不妥可以提出的吖
知识点一:sizeof()和strlen()的异同
先看道题我们再来总结知识点
输出:16 12
解析:I love Linux \0\0\0一共是15个内存,生成字符类型的时候会在后面自动补充一个'\0',所以是16
strlen是以遇到的第一个'\0'为结束符, I love Linux为12个字符长度
显而易见,这道题考查的是与sizeof()和strlen()相关的知识点,我们来总结一下
学以致用,我们来练习一道题
输出结果:26 27
!!你对了吗,没有对的话好好看一下知识点哦
知识点二:unsigned int:
无符号整型,表示从0开始到2^32-1的所有整数,
unsigned后面的int可以省略
递减运算符–(顺便也把递增运算符++了解一下叭)
知识点三:递增递减运算符
“++”和“– –”是两个很特殊的运算符,它们是单目运算符,这个算子还必须是变量。这两个运算符分别叫做递增和递减运算符他们的作用就是给这个变量+或者-1。
例如:
count++:
count +=1
count=count+1
前缀后缀
●++和–可以放在变量的前面,叫做前缀形式,也可以放在变量的后面,叫做后缀形式。
·a++的值是a加I以前的值,而++a的值是加了I以后的值,无论哪个,a自己的值都加了I了。
知识点四:交换两个整数的三种方式
解析:
(1) 使用了一个中间变量 ,先把a的值赋给中间变量,再把b的值赋给a,再把中间变量的值赋给b;
(2) 通过数学运算 将两个变量的值进行交换;
(3) 按位异或(^) :两者相同为0,不同为1;用异或运算讲a与b相应位若相同记为0,不同记1,其值给a,再将b与a进行异或运算实现b到a的转变,再用a与b进行异或运算实现a到b的转变.
按位异或小小知识点
知识点五:static关键字
先看题
输出结果:1,1(多次执行b的值会一直是1,a的值会一直增加)
解析:使用static修饰符定义静态局部变量,它的生命周期是整个应用程序的运行时间,只会被初始化一次,所以第一次执行的时候a的值是1,多次执行a的值就会递增。
知识点总结
♞static关键字的功能
①先来介绍它的第一条也是最重要的一条:隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
②static的第二个作用是保持变量内容的持久。 存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
③static的第三个作用是默认初始化为0 。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
☞学以致用,做道题叭
输出结果:5 5
BUT!!多次执行num的值会按照5的倍数递增5,10,15
拓展小知识(芝士 就是力量)
★static全局变量和普通全局变量区别:static全局变量和普通全局变量存储方式都为静态存储方式,区别在于各自的作用域,普通全局变量的作用域是整个源程序,,当一个源程序由多个源文件组成时,普通全局变量在各个源文件中都有效,而静态全局变量则限制了作用域,只在定义该变量的源文件中有效.
★static局部变量和普通局部变量区别:static局部变量和普通局部变量的存储方式即生存期不同,static局部变量具有全局的生存期, 只初始化一次, 离开函数后仍然存在, 具有函数内的局部作用域,是特殊的全局变量.
★static函数和普通函数区别:static函数与普通函数作用域不同,仅在本文件.只在当前源文件中使用的函数应该说明为内部函数即static修饰的函数,内部函数应该在当前源文件中说明和定义.对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
知识点六:printf函数的返回值
例题:
输出结果: Xiyou Linux Group2019
解析:
函数嵌套,先打印了一个空字符,之后打印Xiyou Linux Group2,0通过%d被打印出来,19作为返回值被最后一次printf函数打印出来
知识点总结
printf()函数也有一个返回值,它返回打印字符的个数.
大部分C函数都有一个返回值,这是函数计算并返回给主调程序的值.可以把返回值赋给变量,也可以用于计算,还可以作为参数传递.
知识点七:宏定义#define
输出结果:3
解析:
你是不是认为结果是4但是,不是这样啦,应该是1+1*1+1=3因为这里没有(),所以先算乘法,如果是4应该这样写:#define X(a+b)
知识点总结
宏定义一般形式:
#define 标识符 字符串
“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。
“标识符”为所定义的宏名。
“字符串”可以是常数、表达式、格式串等。
例如:#define M (a+b)它的作用是指定标识符M来代替表达式(a+b)。
在编写源程序时,所有的(a+b)都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(a+b)表达式去置换所有的宏名M,然后再进行编译。
注意:定义M来替代表达式(a+b),若 s= M * M 在预处理时经宏展开后该语句变为: S=(a+b)(a+b)注意的是,在宏定义中表达式(a+b)两边的括号不能少 ,否则会发生错误。
☞啥也不多说,练题
输出结果:11
我猜你肯定对了,所以不用我多说*~*
知识点八:按位运算and逻辑运算
输出结果:
x=-1,y=4,t=-1
x=0,y=5,t=1
知识点总结:
递增递减运算符(第一题中有所以在这里就不说啦)
按位运算安排起来
看完清清楚楚的图,现在你是明明白白的叭ฅ˙Ⱉ˙ฅ
☞练一道题就更明白啦
小小解析:十进制的7对应二进制的111
所以呢这段代码的功能就是把十进制转化成二进制
下面是逻辑运算啦
逻辑运算的结果
在编程中,我们一般将零值称为“假”,将非零值称为“真”。逻辑运算的结果也只有“真”和“假”,“真”对应的值为 1,“假”对应的值为 0。
①与运算(&&)参与运算的两个表达式都为真时,结果才为真,否则为假。
例如:
5&&0
5为真,0为假,相与的结果为假,也就是 0。(5>0) && (4>2)
5>0 的结果是1,为真,4>2结果是1,也为真,所以与的结果为真,也就是1。
②或运算(||)参与运算的两个表达式只要有一个为真,结果就为真;两个表达式都为假时结果才为假。
例如:
10 || 0
10为真,0为假,相或的结果为真,也就是 1。(5>0) || (5>8)
5>0 的结果是1,为真,5>8 的结果是0,为假,所以相或的结果为真,也就是1。
③非运算(!)参与运算的表达式为真时,结果为假;参与运算的表达式为假时,结果为真。
例如:!0
0 为假,非运算的结果为真,也就是 1。
!(5>0)
5>0 的结果是1,为真,非运算的结果为假,也就是 0
知识点九:结构体struct
虽然排列顺序不同,但是结果都是16.
知识点总结
结构体在为其内部的变量分配内存时,遵循边界对齐原则。边界由结构体中最大数据类型决定。之所以要进行边界对齐,是为了防止有些数据同时占据两个相邻的边界,导致数据总线在访问时要访问两次,提高了工作效率。
结构体字节对齐一般满足这三个条件:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成 员之间加上填充字节。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员 之后加上填充字节。
知识点十:数据的溢出
输出结果:没有给a赋值,运行程序时编译器会报错,如果忽略,则ch输出-1
解析and知识点总结:
char类型的取值范围为-128~127,超出范围就会溢出,上溢是从-128往回,下溢则是从128往回;
255是一个整数,在计算机中存储数据采用补码的形式,而正数原码补码相同,
例如 :
原码 1111 1111
反码 1111 1111
补码 1111 1111
用char来看待这个二进制数字,由于符号位为1,则为负数,负数由补码求原码
补码 1111 1111
原码 1000 0001
所以呢,1111 1111用int来看是255
用char来看是-1.
知识点十一:const关键字
我们先来看一道题
请找出下面的代码的错误
int a =1;
int const b=2;
const int c=3;
void func0(int n){
n+=1;
n=a;
}
void func1(int*n){
*n+=1;
n=&a;
}
void func2(const int *n){//const*n前面表示不可以修改指针n所指向的内容
*n+=1;//不能赋值
n=&a;
}
void func3(int *const n){//const 在n前面表示不可以改变指针n所指向的地址
*n+=1;
n=&a;//不可以改变指向
}
void func4(const int *const n){//表示既不可以改变n所指向的内容,也不可以改变n所指向的地址
*n+=1;//不能赋值
n=&a;//不能改变指向
}
错误我已经在题目中指出,现在我们就来总结一下const的用法
const限定符,它把一个对象转换成一个常量,而常量不能修改,所以根据const修饰的对象,可以确定对象是否可以被修改。
例如:
char*const p:指向char的常指针,指针须在声明时就初始化,之后不可以改变它的指向
char const*和const char *p:是同一种char类型指针,p所指向的内容不可以修改
char const* const p: const修饰p和*p,所以p的值和p所指向的内容都不可以修改
知识点十二:指针与数组的理解
第一行:定义了一个int类型的变量val并初始化为2018;
第二行:声明了一个int类型的指针变量pi并初始化为2019;
第三行:将val变量的地址赋给pi;(&为取地址符)
第四行:将pi指向的空间所存储的数据改为0.
第二行代码解析:为20个char类型的值请求内存空间,并设置p指向该位置.
第三行代码解析:声明指针变量q,指向p所指向的空间(p和q指向了同一个地址.)
输出结果解析:输入Xiyou空格后改变了*p的值,再输入Linux 回车后改变了*q的值(实际改变 的是同一个地址的数据),之后输入的Linux覆盖了之前输入的Xiyou,所以输出结 果是:Linux Linux
这是我的电脑上的输出结果
解析:
每次执行输出的结果不一致,a分配的内存是动态的,所以a的地址一直变.
a表示数组的首地址
&a表示数组首元素的地址
a+1表示数组首地址加上一个元素 所占的地址大小,
int为4个字节,所以加4(1*4)
&a+1表示加上整个数组的大小,
这里数组尺寸是4,所以+1代表的是地址加上16(4x4).
数组与指针有很多零碎的小知识点,在这里我就不详细解释,有兴趣可以看看 c primer plus这本书
知识点十三:传值与传址的区别
结合下面的程序,简要谈谈传值和传址的区别
#include<stdio.h>
int ver=123;
void func1(int ver)
{
ver++;
printf("ver=%d\n",ver);//1026
}
void func2(int*pr)//传入ver的地址给指针pr
{
*pr=1234;//在此函数内部修改pr指向地址中的值
printf("*pr=%d\n",*pr);//1234
pr=5678;//警告(直接把int型常量赋值给int*指针)
/*printf("*pr=%d\n",*pr);/*pr指向的地址已经被修改,其值不确定*/
printf("ver=%d\n",ver);//123(全局变量)
}
int main()
{
int a=0;//函数内部变量
int ver=1025;
for(int a=3;a<4;a++)
{
static int a=5;//静态全局变量(仅在此循环中起作用)
printf("a=%d\n",a);//5
a=ver;
printf("a=%d\n",a);//1025
func1(ver);
int ver=7;//仅在此循环中起作用
printf("ver=%d\n",ver);//7
func2(&ver);
}
printf("a=%d\tver=%d\n",a,ver);//0(传值) 1025(传值)(函数内部变量会覆盖全局变量)
return 0;
}
知识点总结
传值:
1)在传值中函数中函数参数压栈的参数的副本,任何的修改在副本上作用,没有作用在原来的变量上
2)传值时就是在内存中新开辟一个空间,将值赋给这个新开辟的空间,其生命周期为该函数调用结来时释放该空间,计算结果不影响原调用数据内存空间的值。
传址:
同样新开辟一个空间,但不同的是将所用数据空间的内存地址存在新开辟的空间中即指针,对原数据进行操作,操作后结果影响原数据。
知识点十四:c语言中变量的生命周期
全局变量:
进程开始时创建,进程结束时销毁,在代码编译后,直接将其初始值写入到执行文件中,创建时按照定义时的初始值进行赋值
局部变量和参数变量:
进入函数时创建,退出函数时销毁
全局静态变量:
定义一个全局变量并使用static关键字修饰时,这个变量就成了全局静态变量
它的生命周期和全局变量相同,但作用域被限制在文件内
静态局部变量:
在函数内使用static关键字修饰一个变量时,这个变量就为静态局部变量
它的生命周期同全局变量相同,作用域被限制在函数内。
知识点十五:两种字节序(大.小端)
字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序。
小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处(低低高高)
大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处(高低低高)
了解了概念,我们可以通过这段代码来看一看我们的电脑是哪种字节序
#include<stdio.h>
int main()
{
short int x=0x1122;
char x0,x1;
x0=((char*)&x)[0];
x1=((char*)&x)[1];
printf("%#x",x0);
return 0;
}
//如果输出0x22则为小端
//输出0x11则为大端
学以致用,看题看题*~*
#include<stdio.h>
int main()
{
int data[]={0x636c6557,0x20656d6f,0x78206f74,
0x756f7969,0x6e694c20,0x67207875,
0x70756f72,0x32303220,0x00000a31};
puts((const char*)data);//输出结果:Welcome to xiyou Linux group 2021
/*相当于:0x57 0x65 0x6c 0x63 0x6f 0x6d 0x65 0x20
0x74 0x6f 0x20
0x78 0x69 0x79 0x6f 0x75 0x20
0x4c 0x69 0x6e 0x75 0x78 0x20
0x67 0x72 0x6f 0x75 0x70 0x20
0x32 0x30 0x32 0x31*/
return 0;//大小端
}
知识点十六:斐波那契数列相关知识
#include<stdio.h>//头文件
int main()//主函数,程序的入口
{
int i,f1,f2,f3,row; //定义变量
f1=1,f2=1; //变量初始化
printf("输入需要输出的行数:");//提示语句
scanf("%d",&row); //键盘输入行数
printf("%d\n%d\n",f1,f2); //先输出第一行和第二行
for(i=1;i<row-1;i++) ///循环控制后row-2行
{
f3=f2+f1; //第3行的值是前面两行之和
printf("%d\n",f3);
f1=f2; //变量赋值
f2=f3;
}
}
一题多解(使用递归来解决)
#include<stdio.h>
int f(int n)
{
if(n==1||n==2)
return 1;
else
return f(n-1)+f(n-2);
}
int main()
{
int i;
scanf("%d",&i);
printf("%d",f(i));
return 0;
}
知识点十七:冒泡排序以及选择排序
冒泡排序
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
算法描述
①比较相邻的元素。如果第一个比第二个大,就交换它们两个;
②对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
③针对所有的元素重复以上的步骤,除了最后一个;
④重复步骤1~3,直到排序完成。
稳定性
在相邻元素相等时,它们并不会交换位置,所以,冒泡排序是稳定排序。适用场景冒泡排序思路简单,代码也简单,特别适合小数据的排序。但是,由于算法复杂度较高,在数据量大的时候不适合使用。
代码优化
在数据完全有序的时候展现出最优时间复杂度,为O(n)。其他情况下,几乎总是O( n2 )。因此,算法在数据基本有序的情况下,性能最好。要使算法在最佳情况下有O(n)复杂度,需要做一些改进,增加一个flag的标志,当前一轮没有进行交换时,说明数组已经有序,没有必要再进行下一轮的循环了,直接退出。
直接看代码
#include<stdio.h>
#include<stdbool.h>
int main()
{
int a[10];
int n;
int temp;
bool flag=true;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
for(int i=0;i<n-1&&flag;i++)
{
for(int j=0;j<n-1-i;j++)
{
flag=false;
if(a[j]>a[j+1])
{
flag=true;
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
for(int i=0;i<n;i++)
{
printf("%d ",a[i]);
}
return 0;
}
下面我们来了解了解选择排序
选择排序
选择排序是一种简单直观的排序算法,它也是一种交换排序算法,和冒泡排序有一定的相似度,可以认为选择排序是冒泡排序的一种改进。
算法描述
①在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
②从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
③重复第二步,直到所有元素均排序完毕。
稳定性
用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的。不过,一般提到排序算法时,大家往往会默认是数组实现,所以选择排序是不稳定的。
适用场景
选择排序实现也比较简单,并且由于在各种情况下复杂度波动小,因此一般是优于冒泡排序的。在所有的完全交换排序中,选择排序也是比较不错的一种算法。但是,由于固有的O(n2)复杂度,选择排序在海量数据面前显得力不从心。因此,它适用于简单数据排序。
#include<stdio.h>//头文件
int main()//主函数
{
int i,j,min,temp,array[11];//定义整型变量和数组
printf("输入数据:\n");//提示语句
for(i=1;i<=10;i++)//依次键盘录入10个数据
{
printf("array[%d]=",i-1);//数组下标从0开始
scanf("%d",&array[i]);
}
printf("\n");//换行
printf("原样输出:\n");//提示语句
for(i=1;i<=10;i++)//将键盘录入的10个数原样输出
{
printf("%5d",array[i]);
}
printf("\n");//换行
for(i=1;i<=9;i++)
{
min=i;//把第一个数作为最小的
for(j=i+1;j<=10;j++)
{
if(array[min]>array[j])//判断大小,小的为min
{
min=j;
}
}
temp=array[i]; //大小交换
array[i]=array[min];
array[min]=temp;
}
printf("排序输出:\n");//提示语句
for(i=1;i<=10;i++)//输出排序后的10个数
{
printf("%5d",array[i]);
}
printf("\n");//换行
return 0;//主函数返回值为0
}
下面的知识点大多数都是文字描述性的,有一丢丢枯燥,你可以大概了解了解哈
知识点十八:Linux命令ls的输出解释
先看一道题
-al 指各项说明,查看设备是否具有读写权限.
第一列指文件类型及权限;
第二列指链接占用的节点;
第三列指文件所有者;
第四列指文件所有者的用户组;
第五列指文件大小;
第六列指文件的创建时间或者最近修改时间;
第七列是文件名称.
拓展小知识🦋🦋🦋🦋
在Linux系统中有许多系统默认的目录,这些目录按照不同的用途而放置了特定的文件:
bin:该目录存放最常用的基本命令,比如拷贝命令cp、编辑命令vi删除命令rm等。
boot:该目录包含了系统启动需要的配置文件、内核(vmliuxz)和系统镜像(initrdimg)等。
dev:该目录下存放的是Linux中使用或未使用的外部设备文件(fd代表软盘,hd代表硬盘等),使用这些设备文件可以用操作文件的方式来操作设备。
etc:该目录下包含了所有系统服务和系统管理使用的配置文件;比如系统日志服务的配置文件 syslogconf,系统用户密码文件passwd等
home:该目录下包含了除系统管理员外的所有用户的主目录用户主目录一般以用户登陆帐号命名。
lib:该目录下包含了系统使用的动态连接库(*so)和内核模块(在modules下)。
lost+found:该目录包含了磁盘扫描检测到的文件碎片,如果你非法关机,那么下次启动时系统会进行磁盘扫描,将损坏的碎片存到该目录下。
mnt:该目录下包含用户动态挂载的文件系统。如果要使用光盘,U盘都一般应该将它们安装到该目录下的特定位置。
proc:该目录属于内存映射的一个虚拟目录,其中包含了许多系统现场数据,比如进程数,中断情况,cpu信息等等,其中的信息都是动态生成的,不在磁盘中存储。
root:该目录是系统管理员(root用户)的主目录。
sbin:该目录下包含系统管理员使用的系统管理命令,比如防火墙设置命令iptable,系统停机命令
halt等 tmp:该目录下包含一些临时文件。
usr:该目录下一般来说包含系统发布时自带的程序(但具体放什么东西,并没有明确的要求)。其中最值得说明的有三个子目录
第一个:/usr/src:Linux内核源代码就存在这个目录
第二个:/usr/man:Linux中命令的帮助文件
第三个:/usr/local:新安装的应用软件一般默认在该目录下
var:该目录中存放着在不断扩充着的信息,比如日志文件。
知识点十九:c语言文件到可执行文件的理解
分为四个阶段:
(1)预处理阶段。预处理器(cpp)根据字符#开头的命令,修改原始的C程序。
(2)编译阶段。将c语言文件从高级语言转为汇编语言。
(3)汇编阶段。将汇编语言转化为二进制语言。
(4)链接阶段。将使用的头文件与本文件链接起来。
GNU 计划。
GNU 全称 GNU's Not UNIX,又被称为“革奴计划”,由理查德·斯托曼于 1983 年发起。GNU 计划的最终目标是打造出一套完全自由(即自由使用、自由更改、自由发布)、开源的操作系统,并初步将其命名为 GNU 操作系统。在 Linux 内核的基础上,GNU 计划开发了很多系统部件,GCC 就是其中之一
GCC 的全拼为 GNU C Compiler
即 GUN 计划诞生的 C 语言编译器,最初 GCC 的定位确实只用于编译 C 语言。但经过这些年不断的迭代,GCC 的功能得到了很大的扩展,它不仅可以用来编译 C 语言程序,还可以处理 C++、Go...等多种编译语言编写的程序。
很感谢你能够耐心看到结尾呐,最后不得不感叹学姐学长的智慧
最后的最后把我喜欢的句子分享给大家吖
我们深知前景光明辽阔,
但前路不会平坦。
世间因少年挺身向前而更加瑰丽🦋