系统开发工程师
一、单选题共14题,合计70分
1(5分)C语言的标识符只能由字母、数字和下划线三种字符组成,且首字符( A ) [单选题] *
A. 必须为字母或下划线
B. 必须为下划线
C. 必须为字母
D. 可以是字母、数字和下划线中的任一种字符。
2(5分)他人修改源代码后必须开源的许可证有 ( A ) [单选题] *
A. LGPL
B. BSD
C. Apache
D. MIT
3(5分)列出当前目录下所有.h的文件,不含子目录 [单选题] * A
A. ls *.h
B. find . -name *.h
C. find . -name *.h$
D. find . |grep h
4(5分)一个类的友元函数可以访问类的( D )成员。
A. 私有成员
B. 保护
C. 公有
D. 以上都正确
5(5分)统计当前目录中头文件总数[单选题] * B
A. find . |grep h
B. find . -name *.h | wc -l
C. find . |grep h |wc -l
D. find . -name *.h
6(5分)下面字面值数据类型说法错误的是?( C )
A. L’a’表示为宽字符型字面值a且类型是wchar_t
# 'a'表示字符a, L'a'表示宽字符型字面值a且类型是wchar_t, "a"表示字符串a, L"a"表示宽字符型字符串a。
B. 10L表示一个长整形数
# 10是一个普通的整数类型字面值, 10u表示一个无符号数, 10L表示一个长整型数, 10uL表示一个无符号长整型数, 012是一个八进制数(对应的十进制数是10), 0xC是一个十六进制数(对应的十进制数是12)。
C. 3.14L表示一个long float类型的扩展精度浮点数
# 3.14是一个普通浮点类型字面值, 3.14f表示一个float类型的单精度浮点数, 3.14L表示一个long double类型的扩展精度浮点数。
D. 10. 是一个浮点数
# 10是一个整数, 10u是一个无符号整数, 10.是一个浮点数, 10e-2是一个科学计数法表示的浮点数,大小为10*10-2 = 0.1.
7(5分)以下不是C语言提供的合法关键字为( B ) [单选题] *
A. switch
B. printf
# printf 是函数不是关键字
C. case
D. default
8(5分)以下程序运行时,若从键盘输入5,则输出结果是( C ) [单选题] *
main(){
int a=5;
if(a++ > 5) // a++ 是先用后加,现在a目前还是5,表达式为假,会执行else。然后a+1 也就是6。
printf("%d\n", a);
else
printf("%d\n", --a); // --a是先减后用,目前a是6, 减一后为5,输出
return 0;
}
A. 7
B. 4
C. 5
D. 6
9(5分)龙芯CPU的架构是( B ) [单选题] *
A. x86
B. mips
C. ARM
D. Alpha
10(5分)以下不是开源许可证的有( C ) [单选题] *
A. MIT
B. GPL
C. CC
D. BSD
11(5分)假设txt_size 是一个无参数函数,它的返回值是int;Unsigned buf_size = 1024;下面哪个定义是合法的?( B )
A. int ia[buf_size];
# 非法,中括号内必须是常量表达式
B. int ia[4*7 -14];
C. int ia[txt_size()];
# txt_size()只有在编译的时候才能确定其值
D. char st[11] = “fundamental”;
# 没有空间存放空字符,结尾需要存一个'\0'。 应定义为 char st[12] = “fundamental” .
12(5分)使用地址作为实参传给形参,下列说法正确的是( D )。
A. 实参是形参的备份
B. 实参与形参无联系
C. 形参是实参的备份
D. 实参与形参是同一对象
# 地址作为实参,表示实参与形参代表同一个对象。如果实参是数值,形参也是普通变量,此时形参是实参的备份。
# 传值和传址的区别
13(5分)对数组名作函数的参数,下面描述正确的是( B )。
A. 数组名作函数的参数,调用时将实参数组复制给形参数组
# 传递的是指向数组首地址的指针变量
B. 数组名作函数的参数,主调函数和被调函数共用一段存储单元
C. 数组名作参数时,形参定义的数组长度不能省略
# 数组长度不起作用。数组传递的是指向数组首地址的指针变量
D. 数组名作参数,不能改变主调函数中的数据
# 传递的是指针,可以改变主调函数的数据
# 传值和传址的区别
14(5分)下面哪一个不是一种临界区保护机制:C
# 临界区的概念是因为并发编程(multiprogram)的出现导致的,当出现多个task、多个cpu、甚至网络中多个服务器对同一个逻辑对象操作时,就会有条件竞争出现,如果设对该逻辑对象的操作为A,此时必须对A做特殊保护,约定对这A的这种特殊保护统称为临界区保护(mutex exclue),称A操作的范围为临界区。
A. 互斥锁
B. 信号量
C. 条件变量
D. 读写锁
考试环境与要求:
云主机
- system: deepin-20
- 用户名:desk 密码:123
考试项目下载地址:
- 使用浏览器访问 http://10.10.23.1/Develop
- 文件名:dev_exam_sys_A.tgz
考试要求:
- 下载考试项目压缩文件到虚拟机/home/desk/Desktop桌面
- 在桌面创建目录devexam, 并解压下载的tgz文件里的考题(如:exam1,exam11等)存放到devexam里(如:/home/desk/Desktop/devexam/{exam1,exam11等})
- 共三道考题
- 题目和考试要求详见各个题目的main.cpp文件的注释说明
- 自行安装考试所需要的程序开发环境和编译依赖
- 使用C,C++语言完成所有考题
A卷题:
第1题:
* -------------------------------------------------------------------------------------------------------------------
* 题目
* -------------------------------------------------------------------------------------------------------------------
* 无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。并将结果按字符串从小到大排序
*
* 示例1:
* - 输入:S = "qwe"
* - 输出:["eqw", "ewq", "qew", "qwe", "weq", "wqe"]
*
* 示例2:
* - 输入:S = "ab"
* - 输出:["ab", "ba"]
*
* 要求:
* 1. 使用纯C语言修改exam.h文件,通过所有单元测试。
* 2. 代码逻辑符合题目要求,判卷评分有更多单元测试项。
* 3. 输出结果按照字典顺序排序。
* 4. exam.h以外的文件不得修改,修改会被覆盖。
#ifndef EXAM_H
#define EXAM_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** permute_string(const char* input);
void sort(char* S,int len);
void DFS(char *S, int *visited, char *buf, char **res, int *index, int idx);
// main用于自己测试用,考试请勿写入。
int main()
{
const char* input = "ab";
char **s = permute_string(input);
int n = 1;
/* 计算输出长度 */
for (int i = 1; i <= strlen(input); i++) {
n *= i;
}
for(int i = 0; i < n; i++)
{
puts(s[i]);
}
return 0;
}
/* 返回一个以空字符串结尾的char*数组 */
char** permute_string(const char* input)
{
// 获取字符串长度。
int len = strlen(input);
int n = 1;
int i;
/* 计算输出长度 */
for (i = 1; i <= len; i++) {
n *= i;
}
/* 因为题目中给的是const char,这里将const char* 改为char*,方便排序 */
char *S = (char*)malloc(sizeof(char) * (len + 1) );
/* 复制字符串并且排序 */
strcpy(S, input);
// 冒泡排序
sort(S, len);
int N = 7; // 应题目要求
// 返回数组
char **res = (char**)malloc(sizeof(char*) * N);
res[N-1] = 0; // 应题目要求。
// 定义一个数组,用来来标识S[i]是否已经选取
int *visited = (int*)malloc(sizeof(int) * len);
memset(visited, 0, sizeof(int) * len);
// 用于临时存储结果
char *buf = (char*)malloc(sizeof(char) * (len + 1));
buf[len] = '\0';
// res的下角标
int *index = (int*)malloc(sizeof(int));
*index = 0;
DFS(S, visited, buf, res, index, 0);
return res;
}
/* 冒泡排序 */
void sort(char* S,int len){
int i =0, j = 0;
char temp = '\0';
for(i = 0 ; i < len-1; i++)
{
for(j = i+1 ; j < len; j++)
{
if(S[i] > S[j])
{
temp = S[i];
S[i] = S[j];
S[j] = temp;
}
}
}
}
/* DFS */
void DFS(char *S, int *visited, char *buf, char **res, int *index, int idx){
// 1. 截止条件
if(strlen(S) == idx){
res[*index] = (char*)malloc(sizeof(char) * (strlen(S) + 1));
strcpy(res[*index], buf);
(*index)++;
return;
}
// 2. 遍历候选节点
for(int i = 0; i < strlen(S); i++){
// 2.1 筛选
if(!visited[i]){
buf[idx] = S[i];
visited[i] = 1;
DFS(S, visited, buf, res, index, idx+1);
visited[i] = 0;
}
}
}
#endif // EXAM_H
第2题:
* -------------------------------------------------------------------------------------------------------------------
* 题目
* -------------------------------------------------------------------------------------------------------------------
*
* 遵循 https://specifications.freedesktop.org/desktop-entry-spec/latest/ 标准实现一个desktop文件解析工具
*
* 功能要求
* - desktop文件格式无错误时进程退出码为0
* - 实现对desktop文件Name(程序名称)、Exec(可执行文件)、Icon(图标文件)三个字段的解析
* - 需要检测文件的格式,遇到错误时以退出码 1 退出进程
* - 输入接收一个desktop文件的绝对路径,能够输出程序名称、图标文件(图标文件不考虑从系统图标主题中查找)
* - 能够在指定一个desktop文件后启动进程,且允许为其指定参数
*
* 其它要求
* - 使用cmake构建工程
* - 可执行文件位置和名称为:./src/freedesktop
* - testHelper.sh有很少的测试用例,可以帮助做初步验证
*
* 示例
* - a.desktop文件内容为:
* [Desktop Entry]
* Name=A;
* Name[zh_CN]="我是A";
* Exec=cat %f
* Icon=/tmp/a.png
*
* - 输入参数(-d指定desktop文件的绝对路径,-n表示要获取应用程序名称):
* freedesktop -d /home/a/a.desktop -n
* - 输出结果:
* A或我是A(中文环境下,注意应该去掉引号)
*
* - 输入参数(-i表示要获取应用程序的图标)
* freedesktop -d /home/a/a.desktop -i
* - 输出结果(如图标数据不是一个文件路径,则先将图标文件保存为文件后再返回此文件的绝对路径):
* /tmp/a.png
*
* - 输入参数(-e表示要启动此应用程序,其后可根一个或多个传入参数)
* freedesktop -d /home/a/a.desktop -e /home/a/test.txt
* - 输出结果:
* 输出结果为 /home/a/test.txt 文件的内容,也就是Exec命令执行时对标准输出通道所写入的全部数据
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<string>
/* 定义了一个字符串拷贝函数。将str2从beg开始到end赋值给str1; beg和end为下角标 */
void strcopy(char* str1, char*str2, int beg, int end);
int main(int argc, char** argv)
{
char tmp[1024];
int flag[5] = {0};
char *name, *exec, *icon;
int i;
for(i = 1;i < argc; i++)
{
/* -d 文件绝对路径带解析功能 */
if( strcmp( argv[i], "-d" ) == 0 )
{
FILE *p_file = fopen(argv[i+1], "r");
while(fgets(tmp, sizeof(tmp), p_file) != NULL )
{
tmp[strlen(tmp)-1] = '\0';
if( strncmp( tmp, "[Desktop Entry]", 15) == 0 ) flag[0] = 1;
if( strncmp( tmp, "Name=", 5) == 0)
{
char *Name = (char*)malloc(sizeof(char) * ( strlen(tmp) - 5 ) );
strcopy(Name, tmp, 5, strlen(tmp));
if( Name[strlen(Name) - 1 ] == ';' || Name[strlen(Name) - 1 ] == '\n' ) Name[strlen(Name) -1] = '\0';
name = Name;
flag[1] = 1;
}
if( strncmp( tmp, "Exec=", 5) == 0)
{
char *Exec = (char*)malloc(sizeof(char) * ( strlen(tmp) - 5 ) );
strcopy(Exec, tmp, 5, strlen(tmp));
if(Exec[strlen(Exec) - 1 ] == ';' || Exec[strlen(Exec) - 1 ] == '\n') Exec[strlen(Exec) -1] = '\0';
exec = Exec;
flag[2] = 1;
}
if( strncmp( tmp, "Icon=", 5) == 0)
{
char *Icon = (char*)malloc(sizeof(char) * ( strlen(tmp) - 5 ) );
strcopy(Icon, tmp, 5, strlen(tmp));
if(Icon[strlen(Icon) - 1 ] == ';' || Icon[strlen(Icon) - 1 ] == '\n') Icon[strlen(Icon) - 1] = '\0';
flag[3] = 1;
icon = Icon;
}
if( strncmp( tmp, "Name[zh_CN]=", 5) == 0)
{
flag[4] = 1;
}
}
fclose(p_file);
// 遇到错误时以退出码 1
if( ! ( flag[0] == 1 && flag[2] == 1 && flag[3] == 1 && (flag[4] == 1 || flag[1] == 1) ) )
exit 1;
}
}
for(i = 1 ; i < argc; i++)
{
/* -n 获取名称 */
if( strcmp( argv[i], "-n" ) == 0 )
{
std::cout << name << std::endl;
}
/* -i Icon 路径获取 */
if( strcmp( argv[i], "-i" ) == 0 )
{
std::cout << icon << std::endl;
}
/* -e 运行 */
if( strcmp( argv[i], "-e" ) == 0 )
{
std::string s = "cat ";
std::string temp;
int j = i + 1;
while( argv[j] != NULL )
{
temp = argv[j];
s += temp;
s += " ";
j++;
}
char cmdTemp[1024];
strcpy(cmdTemp,s.c_str());
FILE *pp = popen(cmdTemp, "r");
char tmp[1024];
while(fgets(tmp, sizeof(tmp), pp) != NULL)
{
std::cout << tmp ;
}
pclose(pp);
}
}
return 0;
}
void strcopy(char* str1, char*str2, int beg, int end)
{
int i;
for( i = 0; i < end - beg + 1 ; i++ )
{
str1[i] = str2[ beg + i];
}
}
第3题:(30分)
-------------------------------------------------------------------------------------------------------------------
题目
-------------------------------------------------------------------------------------------------------------------
编写一个程序,从命令行读取一个软件包的名称,调用“dpkg -L”命令获得该软件包的文件列表,并检查所有文件。
要求:
1. 使用cmake和c/c++开发
2. 可执行文件名称和位置是./src/pkgverify
3. 检查该软件包所有文件,确认:
- 路径存在
- 文件存在
- 如果是符号链接,链接的文件也存在
- /usr路径下的文件,确认文件属主都是root
4. 输出所有有问题的文件或路径名
5. 由于qDebug/qInfo等输出方式会添加多余字符,且不在标准输出上,与考试要求不符,请谨慎使用
6. testHelper.sh有很少的测试用例,可以帮助做初步验证
示例:
如果/etc/cups路径被删除了,则运行结果如下:
pkgverify cups
/etc/cups
/etc/cups/snmp.conf
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
int main(int argc, char** argv)
{
// 调用系统命令,最小化数组,节省空间
int len = strlen(argv[1]) + 9;
char str1[len] = "dpkg -L ";
strcat(str1, argv[1]);
// 调用系统命令,并取得返回值
FILE *pp = popen(str1, "r");
if (!pp)
return 1;
// 返回值数组
char tmp[1024];
// 初始化stat结构体buf、返回值(retRes用于判断文件是否存在);
// buf2 用于链接文件的返回存储。事实上这里仅仅为了调用,并不进行实际上的作用;
int retRes = 0;
struct stat buf;
char buf2[1024];
// 逐行读取所有的dpkg -L返回值
while(fgets(tmp, sizeof(tmp), pp) != NULL)
{
// 末尾包含 "\n",更改为"\0"结束,后面可能会有问题。
tmp[strlen(tmp)-1] = '\0';
retRes = lstat(tmp, &buf);
// 判断 文件和文件路径 是否存在;
if(retRes == 0) // 存在
{
// - 如果是符号链接
if( S_ISLNK(buf.st_mode) )
{
// 检测链接文件是否存在,不存在输出
int retResult = readlink(tmp,buf2,sizeof(buf2));
if(retResult == -1)
{
std::cout << tmp << std::endl;
}
}
// - /usr路径下的文件,确认文件属主都是root
if( strncmp("/usr", tmp, 4) == 0 )
{
if( buf.st_uid !=0 && buf.st_gid != 0)
{
std::cout << tmp << std::endl;
}
}
}
else // 文件不存在
{
std::cout << tmp << std::endl; // can join each line as string
}
}
pclose(pp);
return 0;
}
相关知识:
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小)
unsigned long st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
// 先前所描述的st_mode 则定义了下列数种情况:
S_IFMT 0170000 文件类型的位遮罩
S_IFSOCK 0140000 scoket
S_IFLNK 0120000 符号连接
S_IFREG 0100000 一般文件
S_IFBLK 0060000 区块装置
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符装置
S_IFIFO 0010000 先进先出
S_ISUID 04000 文件的(set user-id on execution)位
S_ISGID 02000 文件的(set group-id on execution)位
S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限
S_IWGRP 00020 用户组具可写入权限
S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限
S_IWOTH 00002 其他用户具可写入权限
S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLNK (st_mode) 判断是否为符号连接
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode) 是否为目录
S_ISCHR (st_mode) 是否为字符装置文件
S_ISBLK (s3e) 是否为先进先出
S_ISSOCK (st_mode) 是否为socket
int main() {
struct stat buf;
stat("/etc/hosts", &buf);
}