面试题

Part01

以下代码会产生什么结果

char szstr[10];  
strcpy(szstr,"0123456789");  

长度不一样,会产生段错误

数组和链表的区别

数组:数据顺序存储,固定大小;
链表:数据可以随机存储,大小可动态改变

以下代码会产生什么结果

void main()  
{  
	char aa[10];  
	printf(“%d”,strlen(aa));  
}

sizeof()和初不初始化,没有关系,strlen()和初始化有关,打印结果值未知。

sizeof(A) = ?

struct  A  
{  
	char t;  
	char k;  
	unsigned short i;  
	unsigned long m;  
};

32位系统中结果为8,但是在64位系统中结果为16
long 在32位系统中占4字节,而在64位系统中占8字节

sizeof(A) = ?

struct  A  
{  
	char str;  
	short x;  
	int num;  
};

8

sizeof(A) = ?

struct  A  
{  
	char str;  
	int num;  
	short x;
};

12

程序哪里有错误

void swap( int* p1,int* p2 )
{
	int *p;
	*p = *p1;
	*p1 = *p2;
	*p2 = *p;
}

p为野指针,会导致内存泄漏,因为没有指定他的指向,正确的是用int* p = malloc(sizeof(int));并且使用free(p)

c和c++中的struct有什么不同?

c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的struct可以。c++中struct和class的主要区别在于默认的存取权限不同,struct默认为public,而class默认为private。

ptr 和 (*(void**))ptr的结果是否相同?(其中ptr为同一个指针)

(void )ptr 和 ((void**))ptr值是相同的

要对绝对地址0x100000赋值,我们可以用 *(unsigned int*)0x100000 = 1234; 那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?

首先要将0x100000强制转换成函数指针,即:
(void ( * )( ))0x100000
然后再调用它:
((void ( * )( ))0x100000)();

void myFunction() {
    // 函数体
}

int main() {
    // 将整数0x100000强制转换为函数指针
    void (*functionPtr)() = (void (*)())0x100000;

    // 调用函数指针所指向的函数
    functionPtr();

    return 0;
}

Part02

下面几个图中的程序有什么问题

image-20230505211617142

这里GetMemory(char *p)是使用的一级指针,但是传入的是一个一级指针,我们知道,要修改一级指针的指向,需要使用二级指针来实现,所以这里我们可以改成GetMemory(char**p),并且在下面也需要使用*p来操作,而且再Test中调用GetMemory中调用&str

image-20230505214635125

结果可能乱码。因为p在函数中定义,他的生命周期只有函数周期,当return后,其申请的空间都被释放,所以在Test中的str指向的空间是不安全的

image-20230505215104921

可以输出hello,但是会内存泄漏,因为没有回收malloc申请的空间

image-20230505215723566

free(str)后,str指向的空间被释放,但是并没有让str==NULL所以if语句不能生效(即str为野指针),所以会执行其中的语句

关键字volatile有什么含意? 并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量

头文件中的 ifndef/define/endif 干什么用?

防止该头文件被重复引用。

#include <filename.h> 和 #include “filename.h” 有什么区别?

对于#include <filename.h> ,编译器从标准库路径开始搜索filename.h ; 对于#include “filename.h”,编译器从用户的工作路径开始搜索 filename.h

嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;

Part03

const 有什么用途

可以定义 const 常量

const 可以修饰函数的参数、返回值,甚至函数的定义体。被 const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

static有什么用途

限制变量的作用域(static全局变量)

设置变量的存储域(static局部变量)

细节:

修饰全局变量

​ 当static修饰全局变量时,其作用范围将限制在当前文件内部,即该变量在其他文件中不可见。具体来说,使用static修饰全局变量会产生以下效果:

​ 作用域限定:static关键字将全局变量的作用域限制在当前文件内部。这意味着其他文件无法直接访问或使用该变量,因为该变量的可见性仅限于当前文件。

​ 隐藏名称冲突:当多个文件中存在同名的全局变量时,使用static修饰可以避免名称冲突。每个文件中的static全局变量都有其独立的命名空间,不会与其他文件中的同名变量产生冲突。

​ 防止外部访问:static修饰的全局变量对外部代码不可见的。这可以增加代码的安全性和封装性,防止外部代码直接修改全局变量的值,从而更好地控制和保护数据。

​ 需要注意的是,static修饰全局变量仅在当前文件内部有效,并不会改变其存储方式或生命周期。该变量仍然是全局的,只是其可见性受到限制。

修饰局部变量

​ 静态生存期:被static修饰的局部变量具有静态生存期,即在程序执行期间一直存在,而不是在每次进入和离开变量作用域时创建和销毁。它在程序启动时初始化,并保留其值直到程序结束

​ 作用域限定:static关键字同样限定了局部变量的作用域,使其仅在定义它的代码块内可见。与普通局部变量不同的是,static局部变量的作用域不会随着代码块的执行结束而结束,它的作用域延伸到整个函数

​ 持久性:由于具有静态生存期,static修饰的局部变量的值在函数调用之间保持持久。当函数再次调用时,该变量会继续保持其上一次调用结束时的值。这对于需要在函数调用之间保留状态或共享数据的情况非常有用。

​ 需要注意的是,static修饰的局部变量仅在其所属函数内部可见,其他函数无法直接访问或修改该变量。这为实现函数内部的私有数据或状态提供了一种有效的方式,并避免了变量名与其他函数中的变量名冲突的问题。

修饰函数

​ 在C语言中,static关键字可以修饰函数。使用static修饰函数时,函数的作用范围仅限于它所在的源文件。即只有在同一源文件内的其他函数才能调用它。这有助于封装源文件内部的实现细节,使其不被其他源文件所看到或使用,从而增强了代码的模块化。这个特性在写库代码或只想在一个文件内使用某个特定函数时非常有用。

堆栈溢出一般是由什么原因导致的

没有回收垃圾资源

如何引用一个已经定义过的全局变量

可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

//这是使用使用头文件的方式引用全局变量
// global_var.h
#ifndef GLOBAL_VAR_H
#define GLOBAL_VAR_H

extern int global_var; // 声明在其他源文件中定义的全局变量

void print_global_var(); // 声明在其他源文件中定义的函数

#endif // GLOBAL_VAR_H

// other.c
#include <stdio.h>
#include "global_var.h"

int global_var = 42; // 定义全局变量

void print_global_var() {
    printf("In other.c: global_var = %d\n", global_var);
}

// main.c
#include <stdio.h>
#include "global_var.h"

int main() {
    printf("In main.c: global_var = %d\n", global_var);
    print_global_var();
    return 0;
}
main.c和other.c文件都包含了global_var.h头文件,因此它们都可以访问global_var全局变量。编译和运行此示例的方式与之前相同。此方法可确保在跨多个源文件使用全局变量时代码保持整洁和易于维护。

全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

可以,在不同的C文件中以static形式来声明同名全局变量。可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。

Heap与stack的差别

Heap是堆,stack是栈。
Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。
Stack空间有限,Heap是很大的自由存储区
C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。
程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行。

写一个“标准”宏,这个宏输入两个参数并返回较小的一个

#define Min(X,Y) ((X)>(Y)?(Y);(X));

用宏定义写出swap(x,y),即交换两数

#define SWAP(X,Y) ((X)=(X)+(Y);Y=(X)-(Y);(X)=(X)-(Y));

Part04

关键字volatile有什么含意

防止编译器优化,提示编译器对象的值可能在编译器未监测到的情况下改变。

已知一个数组table,用一个宏定义,求出数据的元素个数

#define CUTTABLE(table) ((sizeof(table))/(sizeof(table[0])));

A.c 和B.c两个c文件中使用了两个相同名字的static变量,编译的时候会不会有问题?这两个static变量会保存到哪里(栈还是堆或者其他的)

static的全局变量,表明这个变量仅在本模块中有意义,不会影响其他模块。
他们都放在静态数据区,但是编译器对他们的命名是不同的。
如果要使变量在其他模块也有意义的话,需要使用extern关键字。

程序的局部变量存在于___中,全局变量存在于____中,动态申请数据存在于___中

程序的局部变量存在于栈(stack)中,全局变量存在于静态数据区中,动态申请数据存在于堆(heap)

用两个栈实现一个队列的功能?要求给出算法和思路

设2个栈为A,B, 一开始均为空.
 入队:
  将新元素push入栈A;
 出队:
  (1)判断栈B是否为空;
  (2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;
  (3)将栈B的栈顶元素pop出;

对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现

c用宏定义,c++用inline(内链函数)

用预处理指令#define 声明一个常数,用以表明1年中有多少秒

#define SECOND_PER_YEAR (365*24*60*60);

Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s * 
 typedef struct s * tPS; 
 以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

一个是宏定义一个是宏替换

define是宏替换,typedef是重命名

具体可以看C高级

在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”

ChatGPT

在C++程序中调用由C编译器编译生成的函数时,需要使用extern "C"来声明这些函数,以告诉C++编译器按照C的函数命名和调用约定进行处理。这是由于C和C++在函数命名和调用约定方面存在一些差异。

C++编译器支持函数的函数重载(function overloading),它会根据函数的参数列表和类型进行函数名的修饰(name mangling)。这意味着相同的函数名可以对应不同的函数实现,只要它们的参数列表不同。这在C++中是允许的,但在C中是不允许的。

当C++编译器遇到一个被extern "C"修饰的函数声明时,它会按照C的命名和调用约定来处理该函数,而不会进行函数重载和修饰。这使得C++能够正确地与由C编译器生成的函数进行链接和调用。

因此,当在C++程序中调用由C编译器编译后的函数时,为了确保函数名的匹配和调用的正确性,需要使用extern "C"来告诉C++编译器按照C的规则进行处理。这样可以保证C和C++代码之间的兼容性和互操作性。

语句for( ;1 ;)有什么问题?它是什么意思

死循环,和while(1)相同

请写出下列代码的输出内容
#include <stdio.h>
int main()
{
	int a,b,c,d;
	a=10;
	b=a++;
	c=++a;
	d=10*a++;
	printf("b,c,d:%d,%d,%d",b,c,d);
	return 0;
}

10,12,120

unsigned char *p1;
         unsigned long *p2;
         p1=(unsigned char *)0x801000;
         p2=(unsigned long *)0x810000;
        请问 p1+5= ;
        	p2+5= ;

0x801005、0x810014 注意这里是0x;十六进制

main()
{
	int a[5]={1,2,3,4,5};
	int *ptr=(int *)(&a+1);
	printf(“%d,%d”,*(a+1),*(ptr-1));
}

在给定的代码中:

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int *ptr = (int *)(&a + 1);
    printf("%d, %d", *(a + 1), *(ptr - 1));
}

输出结果将会是:
2, 5

解释如下:

​ a 是一个整型数组,包含 5 个元素,即 a[0] 到 a[4]。
​ &a 获取数组 a 的地址,这个地址是指向整个数组的指针。
&a + 1 表示将指向数组 a 的指针增加一个数组的长度,即指向数组末尾之后的位置。
​ (int )(&a + 1) 将这个指针转换为 int 类型的指针。
​ *(a + 1) 表示取数组 a 中索引为 1 的元素,即 a[1],所以输出结果中的第一个数字是 2。
​ *(ptr - 1) 表示取指针 ptr 指向的前一个位置的值,即数组 a 的最后一个元素,即 a[4],所以输出 结果中的第二个数字是 5。

Part05

C语言宏替换

#define SQUARE(a)((a)*(a))
int a=5;
int b;
b=SQUARE(a++);

宏在预编译时会以替换的形式展开,仅仅会替换。涉及到宏的地方,不要用++ --,标准中对此没有规定,因此最终结果将会依赖于不同的编译器。执行程序的答案可能是25、也有可能是36

死循环
Unsigned char //无符号字符型 表示范围0~255
char //有符号字符型 表示范围-128~127

嵌入式系统中经常要用到无限循环,你怎么用C编写死循环

while(1){}或者for(;;)

image-20230530224525954

8,10,12,14,16

auto int其实就是int

 int modifyvalue()
     { 
         return(x+=10);
     }
     int changevalue(int x)
     {
         return(x+=1);
     }
     void main()
     {
         int x=10;
         x++;
         changevalue(x);
         x++;
         modifyvalue();
         printf("First output:%dn",x); 
         x++;
         changevalue(x);
         printf("Second output:%dn",x);
         modifyvalue();
         printf("Third output:%dn",x);
     }
输出结果?

12\13\14

注意看函数调用的时候没有接收值,而且不是使用指针传参,所以x始终就是会再x++处改变

C语言中,0取反是多少

-1

因为0用十六进制表示为0x00000000,取反后为0x11111111,然后转化为十进制数,高位是1,所以系统会当作负数来处理,然后根据负数求补码的逆运算,就得到了-1;

下列代码的错误

image-20230530232039233

image-20230530232051690

注意,就是在字符串的操作中要注意/0都要占用位置!

const 修饰的变量是放到内存中的

编译器通常不为普通 const 常量分配存储空间,而是将它们保存在符号表中。 const 定义常量从汇编
的角度来看,只是给出了对应的内存地址,而不是象#define 一样给出的是立即数,所以 const 定义的常
量在程序运行过程中只有一份拷贝,而#define 定义的常量在内存中有若干个拷贝。这使得它成为一个编
译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

image-20230531201751290

C++ const修饰成员函数

#include <iostream>
class Test{
public:
    void a(int x){
        x++;
        std::cout<<x<<std::endl;
    }
    void b (int x)const{
        x=x+10;
        std::cout<<x<<std::endl;
    }
};
int main() {
//    int x=0x11223344;
//    std::cout <<std::hex<< (x>>12) << std::endl;
//    short y = x>>12;
//    y=y&0xff;
//    std::cout<<std::hex<<y<<std::endl;
//    return 0;

const Test test;
//test.a(5); //错误,const修饰的只能在const修饰的对象调用
Test test1;
test1.a(3);
test1.b(44);
}

Part06

请写出 bool flag 与“零值”比较的 if 语句

if(flag)       if(!flag)

请写出 float x 与“零值”比较的 if 语句

const float EPSINON = 0.000001;
if((x>=-EPSINON)&&(x<=EXPSINON))

请写出 char *p 与“零值”比较的 if 语句

if(p==NULL)		if(p!=NULL)

以下为 Linux下的 32 位 C程序,请计算 sizeof 的值

char  str[] = “Hello” ;                                                   
char   *p = str ;                                                           
int     n = 10;                                                               
请计算                                                         		 
(1)sizeof (str ) =                  
(2)sizeof ( p ) =                 
(3)sizeof ( n ) = ?
(4)void Func ( char str[100]){
                ……;
}
sizeof(str) = ?  
(5)void *p = malloc( 100 ); 
请计算sizeof ( p ) =

6 4 4 100 4

用变量a给出下面的定义

一个有10个指针的数组,该指针是指向一个整型数的;
一个指向有10个整型数数组的指针 ;
一个指向函数的指针,该函数有一个整型参数并返回一个整型数;
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整形数
int * a[10];
int (*a)[10]                                             
int (*a)(int);   
int (*a[10])(int)

结构体联合体大小;设有以下说明和定义

typedef union {long i; int k[5]; char c;} DATE;
struct data { int cat; DATE cow; double dog;} too;
DATE max;
则语句 printf("%d",sizeof(struct date)+sizeof(max));的执行结果是:52

字符串拷贝问题

请问以下代码有什么问题:
int main()
{
	char a;
	char *str=&a;
	strcpy(str,"hello");
	printf(str);
	return 0;
}
没有为str分配空间,将会发生异常
    问题出现在将一个字符串复制进一个字符变量指针所指向的地址。虽然可以正确输出结果,但因为越界而发生错误。

int (*s[10])(int) 表示的是什么

int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函数

字符串代码考察

char* s="AAA";
printf("%s",s);
s[0]='B';
printf("%s",s);
有什么错?
字符串指针s指向的是一个字符串常量区的”AAA“,而字符串常量区的内容不允许修改,所以后面s[0]='B',会报错。
所以,最开始声明的时候最好加上const char *s = "AAA";

c和c++中的struct有什么不同

c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的struct可以。c++中struct和class的主要区别在于默认的存取权限不同,struct默认为public而class默认为private

字符串传值

void getmemory(char *p)
{
    p=(char *) malloc(100);
    strcpy(p,"hello world");
}
int main( )
{
    char *str=NULL;
    getmemory(str);
    printf("%s\n",str);
    free(str);
    return 0;
}会出现什么问题
出错。修改后代码如下
void getmemory(char **p)//二级指针接受
{
    printf("%s\n","b");
    *p=(char *) malloc(100);

    strcpy(*p,"hello world");
    printf("%s\n","a");
}
int main( )
{
    char *str=NULL;
    getmemory(&str); //加上&,才表示str本身的地址,否则都是str所指向的内容
    printf("%s\n",str);
    free(str);
    return 0;
}
在 C 语言中,当我们声明 `char *str` 时,`str` 是一个指针变量。其基本作用是存储一个地址,这个地址通常是系统内存中另一个数据结构(在这种情况下是 `char` 类型的数据)的起始位置。

所以,简单来说:

- `str` 是一个指针变量,它存储了一个地址。
- `*str` 是去引用这个指针,也就是获取这个地址对应的内存中存储的值。
- `&str` 则是获取这个指针变量 `str` 自身在内存中的地址。

例如,如果我们有 `char *str = "hello";`,那么 `str` 就是指向字符数组 `"hello"` 第一个字符 'h' 在内存中的地址,`*str` 就是 'h',而 `&str` 是这个指针变量 `str` 自身的内存地址。
    所以,上面不能使用传入str,只能传入str的地址,要取地址&str,并且接受的时候需要使用二级指针ca

C语言中结构体怎么可以像C++中类一样,可以写函数

使用函数指针

中断

中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius) 
{ 
     double area = PI * radius * radius; 
     printf(" Area = %f", area); 
     return area; 
}
  • ISR 不能返回一个值

  • ISR 不能传递参数

  • 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的

  • printf()经常有重入和性能上的问题

无符号数与有符号数相加问题

下面的代码输出是什么,为什么?
void foo(void) 
{ 
     unsigned int a = 6; 
     int b = -20; 
     (a+b > 6)? puts("> 6") : puts("<= 6"); 
}

答案是输出是>6。原因是当表达式中存在有符号类型无符号类型时所有的数都自动转换无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的

%d输出什么,可以输出无符号的整数吗,如果不行,那怎么输出无符号整数

%d是输出有符号整数

%u是输出无符号的整数

ARM的工作模式

  1. 用户模式(User Mode):也称为普通模式,用于执行大多数应用程序的用户空间代码。
  2. 系统模式(System Mode):也称为特权模式,用于执行操作系统内核代码和关键任务。
  3. 快速中断模式(Fast Interrupt Mode):用于处理高优先级的快速中断,比如硬件中断。
  4. 中断模式(Interrupt Mode):用于处理一般的中断请求。
  5. 监视模式(Monitor Mode):用于特定的系统监视和调试任务。
  6. 数据中转模式(Data Abort Mode):用于处理数据访问异常,比如未对齐的内存访问或缺页中断。
  7. 未定义指令模式(Undefined Instruction Mode):用于处理未定义的指令。

ARM的异常

  1. 复位异常(Reset Exception):当处理器启动或复位时触发的异常。
  2. 非屏蔽中断异常(Undefined Instruction Exception):当处理器遇到无法解码的指令时触发的异常。
  3. 软件中断异常(Software Interrupt Exception):通过软件触发的中断。
  4. 外部中断异常(External Interrupt Exception):外部设备触发的中断。
  5. 数据中止异常(Data Abort Exception):当访问数据时发生异常,如未对齐访问、缺页中断等。
  6. 指令中止异常(Prefetch Abort Exception):当处理器无法预取指令或指令缓存发生异常时触发的异常。
  7. 未对齐访问异常(Alignment Exception):当处理器执行未对齐的数据访问时触发的异常。
  8. 协处理器异常(Coprocessor Exception):协处理器执行异常。
  9. 系统调用异常(Supervisor Call Exception):通过软件触发的特权级别的函数调用。
  10. 陷阱异常(Trap Exception):通过特定指令触发的异常,用于调试和监视。

C++友元

友元(Friend)是C++中的一个特性,它允许一个类将其他类或函数声明为友元,从而赋予它们对自身私有成员的访问权限。友元关系打破了类的封装性,使得被声明为友元的类或函数可以直接访问该类的私有成员,包括私有变量和私有函数。

下面是一个简单的例子,演示了如何在C++中使用友元:

#include <iostream>

class MyClass {
private:
    int privateData;

public:
    MyClass() : privateData(0) {}

    friend void FriendFunction(MyClass& obj);
};

void FriendFunction(MyClass& obj) {
    obj.privateData = 42;  // 友元函数可以直接访问私有成员
    std::cout << "FriendFunction accessed privateData: " << obj.privateData << std::endl;
}

int main() {
    MyClass obj;
    FriendFunction(obj);

    return 0;
}

在上述示例中,MyClass声明了FriendFunction函数为友元。这意味着FriendFunction函数可以访问MyClass的私有成员privateData。在FriendFunction函数中,我们修改了obj的私有成员privateData的值,并打印出来。这证明了友元函数可以直接访问类的私有成员。

需要注意的是,友元关系是单向的,即如果类A将类B声明为友元,那么类B并不自动将类A声明为友元。此外,友元关系不具有继承性,即如果类A是类B的友元,那么类C继承自类B并不自动成为类A的友元。

友元的使用应该谨慎,因为它可能破坏了类的封装性,增加了代码的耦合性。应该遵循封装原则,只在必要的情况下使用友元关系。

Part07

read有什么返回值,什么时候返回0

  • 读取管道的时候,当没有数据的时候就会返回0
  • 当设置read为非阻塞IO的时候就会返回0

以下函数输出结果是什么

int main(){
    int x[]={1,2,3,4,5,6,7,8,9};
    int *p=x;
    p+=2;
    printf("%d\n",*(p++));
}

输出结果是3,我一直因为*(p++),是先p++然后再*,I was wrony!,

宏替换,以下代码输出是什么

#include <stdio.h>
#define D(x) (2*x)+3
int main()
{
	int i = 1, j = 2;
	printf("%d\n", D(i+j));
}

结果为7

使用gcc -E test.c 可以看到宏替换后的结果

int main()
{
 	int i = 1, j = 2;
	printf("%d\n", (2*i+j)+3);
}

解释一下linux中的NFS

在Linux操作系统中,虚拟文件系统(Virtual File System,VFS)是一种抽象层,它在实际的文件系统和内核或用户程序之间提供了一个统一的接口。VFS的目的是允许那些使用标准系统调用的应用程序访问各种类型的文件系统,让所有文件系统看起来都像是同一种类型。

VFS主要由两部分组成:逻辑文件系统和设备驱动程序。

  1. 逻辑文件系统:这部分主要处理文件系统的逻辑结构和组织方式。在Linux中,有许多不同类型的文件系统,例如ext4、XFS、Btrfs、NFS等,它们的内部组织和工作方式各不相同。逻辑文件系统主要负责处理这些细节,使得用户和应用程序无需关心文件系统的具体类型,就可以通过统一的接口进行文件操作。

  2. 设备驱动程序:这部分负责和实际的硬件设备进行交互,例如硬盘、SSD、USB设备等。设备驱动程序会将来自逻辑文件系统的读写请求转换为硬件设备可以理解的指令,然后将硬件设备的响应转换回逻辑文件系统可以处理的数据。

因此,VFS充当了用户程序、文件系统和硬件设备之间的“桥梁”,使得它们可以高效、灵活地协同工作。

linux应用层调用open函数的过程

在Linux操作系统中,当应用层调用open()函数来打开一个文件时,该请求最终会经过设备驱动程序,但是这个过程由操作系统的内核来处理,对于应用程序来说,这个过程是透明的。

以下是详细的步骤:

  1. 应用程序调用open()系统调用,传入需要打开的文件名。

  2. 内核接收到这个请求后,会首先通过虚拟文件系统(VFS)找到对应的文件。

  3. VFS确定了该文件属于哪种文件系统(例如,ext4、xfs等),并找到该文件系统对应的具体实现。

  4. 文件系统的实现会确定如何在硬件设备上找到这个文件,这通常涉及到读取一些文件元数据,例如i节点(在UNIX类文件系统中)。

  5. 为了读取硬件设备上的数据,文件系统实现会通过块设备层发出读取请求。这个读取请求最终会被传递到对应的设备驱动程序。

  6. 设备驱动程序会发送硬件指令,从硬件设备上读取数据,然后将数据返回给文件系统实现。

  7. 文件系统实现根据读取的数据确定文件的位置和属性,然后创建一个文件描述符,并返回给调用open()函数的应用程序。

所以,虽然应用程序直接调用的是open()函数,但在这个过程中,设备驱动程序确实参与了处理这个请求。

C++中的static函数有什么用

在C++中,static 关键字主要有四种用法:

  1. 在函数体,一个代码块或类中声明的静态变量具有全局的生命周期。函数或代码块结束时,它们并不会被销毁,而会一直存在,直到程序结束。这意味着,它们的值在多次函数调用之间是持久的。此外,它们只在声明它们的函数或代码块中可见。

  2. 在全局范围内,static 变量或函数在声明它们的文件中是局部的。它们在文件外部是不可见的,这保护了不应该被其他文件访问的代码和数据。

  3. 在类中,静态成员变量是所有对象共享的,而不是每个对象拥有自己的一份。也就是说,如果你修改了一个对象的静态成员变量,那么它将影响所有的对象。静态成员变量在类外部初始化。

  4. 静态成员函数也是类的所有对象所共享的,它没有 this 指针。因为没有 this 指针,所以静态成员函数不能访问非静态成员变量,但可以访问静态成员变量。

注意:C++ 中 static 的用法与 C 语言中类似,但有额外的特性,特别是在面向对象的内容中。

下面的printf打印结果是什么

int main(){
    int i = 2;
    int j = 2;
    int m = 2;
    int n = 2;
    int p = 2;
    int q = 2;
    printf("-(i++)  %d\n",-(i++));  
    printf("-j++  %d\n",-j++);
    printf("-++m  %d\n",-++m);
    printf("-(++n)  %d\n",-(++n));
    printf("(++p)  %d\n",(++p));
    printf("(q++)  %d\n",(q++));
    return 0;
}
int main(){
    int i = 2;
    int j = 2;
    int m = 2;
    int n = 2;
    int p = 2;
    int q = 2;
    printf("-(i++)  %d\n",-(i++));  //-2
    printf("-j++  %d\n",-j++);  //-2
    printf("-++m  %d\n",-++m);  //-3
    printf("-(++n)  %d\n",-(++n));  //-3
    printf("(++p)  %d\n",(++p));  //3
    printf("(q++)  %d\n",(q++));  //2
    return 0;
}

下列对象定义错误的是

A. sturct A{A _a};

B. sturct A{A* _a};

A. sturct A{A& _a};

A. sturct B; struct A{B& _b}; struct B{A& _a};

答案是A

因为在C++中,结构体在定义完成之前是inconplete type(不完全类型),不完全类型是不能定义对象,只能定义引用和指针,或者用于声明函数的形参和返回值类型

mv移动文件,文件的修改时间会不会发生变化

mv移动文件,不会改变文件的修改时间

ulimit -c,是什么作用

ulimnit用于设置shell启动进程所占用的资源。

-c size:设置core文件的最大值,单位:blocks

具有很多C语言的功能,又称过滤器的是

awk

​ AWK 是一种强大的文本分析工具,由 Aho、Weinberger 和 Kernighan 三人在1977年的贝尔实验室发明(他们的姓首字母构成了 AWK 的名字),用于在UNIX/LINUX下对文本和数据进行处理。

​ AWK 可以处理极其复杂的文本操作,并且可以嵌入在 Shell 脚本中使用,也可以写成 AWK 脚本文件。这使得 AWK 成为处理日志,数据流,文本报告等任务的理想选择。

使用方法如下:

使用AWK的基本语法是 awk '/pattern/ {action}' filename,其中:

  • pattern 是你想匹配的模式,这个模式可以是字符串,正则表达式,或者更复杂的逻辑表达式。

  • action 是当模式匹配成功时你希望执行的操作,这个操作可以是打印输出,赋值,计算等等。

  • filename 是你想处理的文件。

以下面的例子为例,设有一个文件test.txt

John    Doe     25
Jane    Doe     23
Alice   Smith   27
Bob     Johnson 22
  • 如果你想输出所有人的名字和年龄,可以运行awk '{print $1, $3}' test.txt$1$2$3 等是 AWK 内置的变量,代表当前行的第1,第2,第3个字段,字段之间默认由空格或制表符隔开。结果会是:
John 25
Jane 23
Alice 27
Bob 22
  • 如果你只想输出年龄大于24的人,可以运行 awk '$3>24 {print $1, $3}' test.txt,结果会是:
John 25
Alice 27

AWK的功能非常强大,可以做的事情远不止这些,包括运算,字符串操作,流程控制等等。对于复杂的任务,你也可以将AWK命令写入一个脚本文件中,然后运行这个脚本文件。

注意这里的例子使用了默认的字段分隔符(空格或制表符),你也可以通过 -F 参数来指定自己的字段分隔符。例如,如果你的文件是以逗号分隔的,你可以运行 awk -F',' '{print $1, $2}' filename

图中的代码运行打印的结果是多少image-20230627225002917

8

注意c语言函数的形参都是指针,一定记住,而且函数都是值传递

new和malloc的区别

newmalloc都用于在堆上动态分配内存,但它们是两个不同的库函数,存在一些重要的区别。

以下是newmalloc之间的主要区别:

  1. 来源new是C++中的操作符,而malloc是C中的库函数。

  2. 内存分配newmalloc都在堆上动态分配内存。new会返回正确的类型的指针,这样就无需进行类型转换。而malloc返回的是void类型的指针,因此需要进行类型转换。

  3. 构造函数和析构函数:当使用new为C++对象分配内存时,它会自动调用对象的构造函数。当使用delete释放内存时,它会调用对象的析构函数。但是,mallocfree不会调用构造函数和析构函数。

  4. 错误处理:当无法分配请求的内存时,new会抛出bad_alloc异常(除非使用了nothrow版本的new,那样就会返回nullptr)。而malloc则会返回NULL或者nullptr

  5. 内存分配的大小:在malloc中,需要手动输入要分配的内存大小。在new中,编译器自动确定类型的大小。

以下是使用newdelete的示例:

int* ptr = new int;  // 分配一个int大小的内存
delete ptr;          // 释放内存

以下是使用mallocfree的示例:

int* ptr = (int*)malloc(sizeof(int));  // 分配一个int大小的内存
free(ptr);                             // 释放内存

综上所述,newdelete提供了更强大且灵活的内存管理方式,更适合于C++的对象模型。而在C语言中,我们则通常使用mallocfree q1` q。