我来总结一些面试题

目录

一、C

1.C语言和C++的区别

2.声明和定义

3.const和define关键字

4.extern关键字

5.static关键字

6.volatile关键字 ——易变的

7.new和malloc,delete和free

8.如何防止重复引用头文件

9.C语言编译过程

10.内存分配

11.内存泄漏

12.指针和引用的区别 

13.char和字符串

14.#include<>和#include" "

15.链表

二、操作系统

1.进程与线程

2.并发与并行

3.用户态和内核态

4.进程间通信方法

CPU进程调度的策略

三、计算机网络

1.五层和七层

2.三次握手

3.四次挥手

4.get与post区别

四、通信协议

1.MODBUS-RTU

2.RS485和RS232

五、Linux

六、STM32

1.测报灯使用的stm32

一、C

1.C语言和C++的区别

C语言面向过程,关注函数的调用和数据的处理,C++继承了 C 语言并增加了面向对象的特性:类、对象、封装、继承、多态。C语言注重底层的数据处理与硬件的直接交互,适用于嵌入式开发和对性能要求较高的场景。

2.声明和定义

  • 变量定义:用于为变量分配存储空间,为变量指定初始值。程序中,变量有且仅有一个定义。
  • 变量声明:用于向程序表明变量的类型和名字。

定义也是声明:当定义变量时我们声明了它的类型和名字。extern声明不是定义:通过使用extern关键字声明变量名而不定义它。变量在使用前就要被定义或者声明。在一个程序中,变量只能定义一次,却可以声明多次。定义分配存储空间,而声明不会。

  1. 当extern只声明没有初始化时:是告诉编译器变量在其他地方定义了;
  2. 当extern声明且有初始化式时,就被当作定义去理解,即使前面加了extern。

3.const和define关键字

define是预编译指令,const是普通变量的定义,define定义的宏是在预处理阶段展开的,而const定义的只读变量是在编译运行阶段使用的。

const定义的是变量,而define定义的是常量。define定义的宏在编译后就不存在了,它不占用内存,因为它不是变量,系统只会给变量分配内存。

const定义的常变量本质上仍然是一个变量,具有变量的基本属性,有类型、占用存储单元。const定义的是变量,而宏定义的是常量,所以const定义的对象有数据类型,而宏定义的对象没有数据类型。所以编译器可以对前者进行类型安全检查,而对后者只是机械地进行字符替换,没有类型安全检查。这样就很容易出问题,即“边际问题”。

const关键字

1.cosnt修饰全局变量:此时全局变量只能使用,不能修改;如果直接修改全局变量,会编译报错;如果用全局变量的地址来修改值,运行时程序异常结束。

2.const修饰局部变量:不能直接通过局部变量进行修改值,会编译报错;可以通过局部变量的地址修改局部变量的值。

3.const修饰指针

(1)int i = 1;int* const p = &i;const修饰了指针p,p无法指向其他地址,但是p所在地址的变量值,也就是*p可以修改。总结:const p,p无法赋值,*p可以赋值,i可以赋值

(2)int i= 1;const int* p = &i;或者int const *p = &i;const修饰了*p,*p就是i,所以*p不能修改,i不能修改,但是p可以修改,也就是p可以再指向其他地址,总结:const *p,p可以赋其他地址的值,*p和i不可以赋值

(3)const int* const p;*p和p都被const修饰了,所以,指针不能变,指针所指的变量也不能变。

4.const修饰数组const int a[]

表示数组中的每个元素都是const int,无法修改,所以必须在初始化的时候进行赋值,否则数组创建之后就无法赋值了。

define关键字:不需要分配空间,在预编译时会直接替换为相应的值或表达式

1.define定义标识符:理解为替换

#define name stuff——用name替换stuff

#define MAX 100                 //定义一个全局变量MAX值为100
#define reg register           //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。

2.define定义宏:理解为带参数的替换,类似函数,但是要区分宏和函数

#define name( parament-list ) stuff——

#define SQUARE1(X) X*X

#define SQUARE2(X) (X)*(X)

SQUARE1(1+4) = 1+4*1+4 = 9

SQUARE2(1+4)=(1+4)*(1+4)=25

宏和函数对比

函数

# 和 ## 区别

#和##是用来操作宏定义中参数的特殊符号。

1. #(字符串化运算符):将参数转换为一个字符串常量;

#define STR(x) #x
 
STR(hello world) // 将会被转换为 "hello world"

2. ##(连接运算符):用于将两个参数连接成一个符号。

#define CONCAT(x, y) x##y
 
int CONCAT(a, b) = 10;
 
// 上面这一行宏定义等价于以下代码
// int ab = 10;

4.extern关键字

简单说extern关键字可以在多个文件中共享变量和函数,方便了模块化编程和代码重用。在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。

用于声明一个变量或函数具有外部链接性(external linkage),即这些变量或函数可以被其他文件访问。用于表示一个变量或函数在其他文件或模块已经定义,也可以在当前文件或模块使用。

用于声明全局变量或函数:在一个文件中声明一个全局变量或函数为extern,表示该变量或函数在其他文件中定义,这样就可以在当前文件中使用该变量或函数。

用于引用全局变量或函数:在当前文件中引用其他文件中已经定义的全局变量或函数。

5.static关键字

一、静态局部变量:本质上是改变存储位置,表现为延长了生命周期。

局部变量静态局部变量
生命周期随着函数的调用而开始,随着函数的结束而结束从定义开始,持续到程序运行结束
作用域在定义它的函数内部

限定在定义它的函数内部,但它可以在函数调用结束后保留其值,供下一次该函数调用时使用。

存储位置静态区(全局区)
初始值定义时不会被自动初始化,在使用之前需要手动为其赋值。在定义时会被自动初始化为0或NULL。

静态局部变量使用场景?

  1. 保持状态信息:当需要在函数调用之间保留某些状态或信息时,静态局部变量非常有用。例如,可以使用静态局部变量来统计函数被调用的次数、保存上一次调用的结果等。
  2. 限制作用域:静态局部变量的作用域仅限于定义它的函数内部,不会对其他函数可见。这样可以避免全局命名空间的污染,同时允许函数内部使用一个独立的状态变量。
  3. 线程安全性:多线程环境下,静态局部变量是线程安全的,因为每个线程独立拥有一份副本,互不干扰。

二、静态全局变量:本质上是改变了链接属性

全局变量静态全局变量
作用域整个源文件,可以被该源文件中的所有函数访问作用域也是整个源文件,但只能在定义它的源文件中访问,其他源文件无法访问。
生命周期从程序开始执行到程序结束,即整个程序的运行期间都存在。

从程序开始执行到程序结束,但它仅限于定义它的源文件内部可见,其他源文件无法访问。

链接属性外部链接内部链接
可见性对整个程序是可见的,可以在不同的源文件中共享数据

对于其他源文件是不可见的,只在定义它的源文件内部起作用。

静态全局变量使用场景?

  1. 实现模块内的变量共享:在一个源文件中需要多个函数之间共享数据,但不希望这些数据被其他文件访问,可以使用静态全局变量。静态全局变量的作用域仅限于定义它的源文件,可以确保数据的私有性和封装性。
  2. 防止命名冲突和污染:全局变量容易引起命名冲突和全局命名空间的污染。使用静态全局变量可以将变量的作用域限制在当前源文件内部,避免与其他文件中的全局变量发生冲突。
  3. 函数局部变量的共享:在某些情况下,可能需要在不同的函数调用之间共享局部变量的值,可以使用静态全局变量来实现。将变量定义为静态全局变量后,不管函数何时被调用,静态全局变量的值都会被保留下来,可以在函数调用之间进行共享。

三、静态函数:本质上是改变了链接属性和作用域

全局函数静态函数
链接属性外部链接内部链接
作用域可以被其他源文件引用。只能在当前源文件中调用该静态函数,其他源文件无法直接调用。

静态函数使用场景?

  1. 辅助函数:将一些只在当前源文件内部使用的辅助函数声明为静态函数,可以避免这些函数被其他源文件引用,减少全局命名空间的污染。
  2. 提供模块内部接口:在模块化编程中,可以将模块内部的一些功能函数声明为静态函数,通过一个公开的接口函数来调用这些静态函数。这样,模块外部只能通过接口函数来访问模块内部的功能,实现了信息隐藏和封装。

6.volatile关键字 ——易变的

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有 volatile 关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。所以遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

volatile关键词影响编译器编译的结果

7.new和malloc,delete和free

newmalloc
类型是关键字,需要编译器支持是库函数,需要头文件支持。
申请大小申请内存无需指定内存大小,编译器会根据类型信息自行计算。由我们计算需要申请的字节数,需要显式指出所需内存的尺寸。
返回类型申请什么类型就返回什么类型并且可以初始化只管申请空间,返回void*需要程序员显示的转换成要用的类型,并且无法初始化
释放deletefree
申请失败抛出bad_ alloc异常返回NULL
重载可以不可以
内存区域自由存储区堆区

8.如何防止重复引用头文件

1.使用条件编译:

#ifndef _TEST_

#include  _TEST_

... ...

 #endif

2.根据编译器的支持使用:#pragma once  

9.C语言编译过程

C程序编译过程概述:C代码通过编译器编译成可执行代码,经历了五个阶段,依次为:预处理、编译、汇编、链接、运行。 图1 编译过程图一、预处理任务:1、删除注释;2、宏替换;3、展开include具体处理...https://blog.csdn.net/Jacky_Feng/article/details/84067991?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169530654016800184193237%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169530654016800184193237&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-84067991-null-null.142%5Ev94%5Einsert_down1&utm_term=c%E8%AF%AD%E8%A8%80%E4%BB%A3%E7%A0%81%E7%BC%96%E8%AF%91%E6%B5%81%E7%A8%8B&spm=1018.2226.3001.4187

10.内存分配

C语言:内存分配---栈区、堆区、全局区、常量区和代码区icon-default.png?t=N7T8https://blog.csdn.net/MQ0522/article/details/114823770

11.内存泄漏

内存泄漏指的是在程序运行过程中,动态分配的内存没有被正确释放或者释放时机不当,导致这部分内存无法再被程序所使用,从而造成内存的浪费和不断增加的内存占用。

导致内存泄漏的情况:

  1. 动态内存分配未释放:通过malloc()、calloc()、realloc()等函数分配的内存,在使用完毕后应该通过free()函数进行释放。如果忘记释放这块内存,就会造成内存泄漏。
  2. 丢失指针:如果一个指针指向了一块动态分配的内存,然后该指针被重新赋值或者丢失,导致之前分配的内存没有释放,也会发生内存泄漏。
  3. 循环引用:在使用垃圾回收机制不可用的语言中,如果存在循环引用的数据结构(如链表、树等),并且没有正确断开这些引用关系,就会导致无法回收的内存泄漏。

避免内存泄漏的措施:

  1. 在动态分配内存后,确保在不再需要时及时释放内存,遵循申请与释放的对应原则。
  2. 注意检查指针的赋值和生命周期,确保指针指向正确的内存区域,并在不再使用时及时将其置为NULL。
  3. 对于循环引用的情况,可以手动断开引用关系,或者使用弱引用、弱代指针等机制来解决。
  4. 使用内存管理工具和调试器来检测和跟踪内存泄漏问题,如Valgrind等。

内存泄漏会导致的问题:

  1. 内存耗尽:重复发生内存泄漏会导致系统的可用内存逐渐减少,最终可能耗尽系统的内存资源,使程序崩溃或系统变得不稳定。
  2. 性能下降:内存泄漏会使程序占用的内存逐渐增加,导致程序运行速度变慢,甚至引起内存碎片化,影响程序的性能。
  3. 资源泄漏:如果内存泄漏发生在使用其他资源的上下文中(如文件句柄、网络连接等),可能会导致资源的泄漏,使得这些资源无法被释放,进而影响系统的可用性。

12.指针和引用的区别 

指针引用
概念C语言中就存在的概念C++中才有的概念
定义和声明int* ptr;int &ref=num;
初始化可以先定义,再初始化,可以重复赋值。必须定义时初始化,一旦被初始化,就不能改变
空值可以指向空地址,表示未指向任何对象不可为NULL,必须始终指向一个对象
多级可以多级只能一级
内存管理需要手动管理,申请和释放不需要管理内存,只是原变量的一个别名
优点更灵活,可以指向空地址和动态分配的内存更简洁、直观,无需操作符,并且更安全,不会出现空指针问题

13.char和字符串

首先char是C语言中的字符类型,表示单个字符,占一个字节,用单引号表示;

另外,C语言中没有字符串类型,所谓字符串可以理解为字符的合集或者字符的数组,可以用双引号表示也可以用数组表示,字符串可以用数组接收,也可以用指针接收。

char arr[20] = "Hello, World!";

char* str = "Hello, World!";

char arr[ ] = {'b','i','t'};

14.#include<>和#include" "

< >:直接去库函数头文件所在的目录下查找;

" " :先在源文件所在的目录下查找,如果找不到,则在库函数的头文件目录下查找。

15.链表

//链表
#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
	//数据域
	int num;//学号
	int score;//成绩
	char name[20];//姓名
	//指针域
	struct student* next;
}STU;

//链表的创建
void link_creat_head(STU** p_head, STU* p_new)
{
	STU* p_mov = *p_head;
	if (*p_head == NULL)//新节点来时,链表为空,新节点就作为头节点
	{
		*p_head = p_new;
		p_new->next = NULL;
	}
	else//新节点来的时候链表不为空,把新节点放到链表尾部
	{//既然是放到尾部,所以需要知道尾在哪?
		while (p_mov->next != NULL)//p_mov的next为NULL时,p_mov就是尾
		{
			p_mov = p_mov->next;//如果不为NULL,就一直往后找
		}
		p_mov->next = p_new;
		p_new->next = NULL;
	}
}

//链表的遍历
void link_print(STU* head)
{
	STU* p_mov;//定义一个新的指针
	p_mov = head;//来保存链表头指针,防止遍历过程中链表被修改
	while (p_mov!=NULL)
	{
		printf("num = %d score = %d name = %s\n", p_mov->num, p_mov->score, p_mov->name);
		p_mov = p_mov->next;//指针后移
	}
}

//链表的释放
//用指针p遍历链表,每次把p赋给q,释放q即可
void link_free(STU **p_head)
{
	STU *pb = *p_head;//定义一个指针,保存头节点的地址
	while (*p_head!=NULL)//头节点不为空就继续释放
	{
		pb = *p_head;
		*p_head = (*p_head)->next;
		free(pb);
	}
}

int main()
{
	STU* head = NULL;
	STU* p_new = NULL;
	int num, i;
	printf("请输入链表的初始个数:\n");
	scanf("%d", &num);
	for (size_t i = 0; i < num; i++)
	{
		p_new = (STU*)malloc(sizeof(STU));
		printf("请输入学号,分数和姓名:\n");
		scanf("%d %d %s", &p_new->num, &p_new->score, &p_new->name);
		link_creat_head(&head, p_new);//将新节点p_new加入链表中
	}

	link_print(head);
}

二、操作系统

1.进程与线程

线程与进程相信大家面试时一定没少被一个问题刁难,那就是进程和线程的区别是什么?这个问题延申开来并不像表面那么简单,今天就来深入一探。开始前先看一组非常传神的图例,相信可以帮助你更好理解进程与线程的概念:1 进程与线程的关系和区别什么是进程进程可以说是一个“执行中的程序”。程序是指令、数据及其组织形式的描述,是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。进程有哪些特征?进程依赖于程序运行而存在,进程是动态的,程序是静态的;进程是操作系统进行_进程和线程的区别https://blog.csdn.net/mu_wind/article/details/124616643?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169470338716800197068700%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169470338716800197068700&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-124616643-null-null.142%5Ev94%5Einsert_down1&utm_term=%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%8C%BA%E5%88%AB%E9%9D%A2%E8%AF%95&spm=1018.2226.3001.4187

2.并发与并行

并发:指的是宏观上两个程序在同时运行,比如单核CPU上的多任务,但是微观上是两个程序在交错执行;并发不能提高计算机的性能,只能提高效率

并行:是物理意义是的多个任务同时运行,比如多核CPU处理多任务,实际上是每个程序都在一个核上运行且互不影响;并行真正提高了计算机效率

3.用户态和内核态

之所以有区分是为了避免操作系统和关键数据被用户程序破坏

内核态:是操作系统管理程序执行时的状态,可以执行包含特权指令在内的一切指令,可以访问系统内所有的内存空间;

用户态:是用户程序执行时的状态,不能访问特权指令,只能访问用户地址空间;

从用户态切换到内核态的方法有三种:系统调用、异常和外部中断

  1. 系统调用是操作系统的最小功能单位,是操作系统提供的用户接口,系统调用本身是一种软中断。
  2. 异常,也叫做内中断,是由错误引起的,如文件损坏、缺页故障等。
  3. 外部中断,是通过两根信号线来通知处理器外设的状态变化,是硬中断。

4.进程间通信方法

        首先是每个进程都有不同的用户地址空间,进程间的通信就是不同进程之间的信息交换和数据共享。进程间通信需要通过内核,在内核中开辟一块缓冲区,进程A把数据从自己的用户空间拷贝到内核缓冲区,进程B可以从内核缓冲区把数据读走,这样就实现了进程间的通信。所以进程间通信的本质就是进程之间看到一份公共资源,所以提供这份公共资源的方式和提供者不同造成了通信方式的不同。

1.管道通信:作用于有血缘关系的进程之间,实现数据传递。是一种半双工的通信方式,数据只能在一个方向上流动。管道中的数据一旦被读走便不存在。父进程通过管道写入数据后,在子进程中可以通过管道读取数据,实现了进程间的通信。

2.共享内存实现通信:是最快的通信方式。通过使多个进程共同访问同一块内存空间,不同进程之间可以及时的看到对方进程对共享内存中数据的更新。(实现的方式是:通过映射同一块物理内存到不同进程的虚拟内存空间中,各个进程可以直接对共享内存读写操作,无需进行额外的数据拷贝。)

3.消息队列

CPU进程调度的策略

是操作系统用来决定将CPU时间片分配给哪个进程

1.先来先服务:

2.短作业优先:

3.优先级调度:

4.时间片轮转:

5.多级反馈队列调度:

6.最高响应比优先:

7.最短剩余时间优先:

三、计算机网络

1.五层和七层

2.三次握手

  1. 第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端 进入syn_sent状态,等待服务端确认。
  2. 第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和 ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。
  3. 第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输 数据了

3.四次挥手

  1. 第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入fin_wait_1状态。
  2. 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1,服务端进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
  3. 第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入Last_ack状态。
  4. 第四次挥手:客户端收到FIN后,客户端进入Time_wait状态,接着发送一个ACK给服务端,确认后,服务端进入Closed状态,完成四次挥手。

4.get与post区别

首先都是HTTP协议中请求数据的方式。

GETPOST
产生数据包1个TCP数据包2个TCP数据包
数据大小限制为 URL 的最大长度,因此传输数据量较小。没有这种限制,可以传输更大量的数据。
工作流程把http header和data一并发送出去,服务器响应200浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
适用于传输少量数据,且数据对安全性要求不高的情况传输大量数据,或者数据对安全性要求较高,如用户登录、密码修改等场景。

四、通信协议

1.MODBUS-RTU

Modbus是应用层软件通讯协议

  1. 物理层:MODBUS-RTU使用串行通信方式,在物理层上使用RS-232、RS-485等标准串口进行数据传输。其中,RS-485多用于长距离通信和多设备共享一个总线的情况。
  2. 数据帧结构:MODBUS-RTU使用了简单而紧凑的数据帧结构。每个数据帧包含了地址、功能码、数据以及错误检测等字段。通常情况下,从站会接收主站发送的请求并做出响应。
  3. 功能码:MODBUS-RTU定义了一系列功能码,用于表示不同的操作类型,例如读取寄存器、写入寄存器等。主站通过发送不同功能码的请求来指示从站执行相应的操作。
  4. 数据格式:MODBUS-RTU使用16进制表示数据,并采用大端字节序(高字节在前)进行传输。数据可以是开关量、模拟量等不同类型的数据。
  5. 错误检测:MODBUS-RTU使用循环冗余校验(CRC)来检测数据传输中的错误,保证数据的可靠性。接收方会对接收到的数据进行CRC校验,判断是否存在错误。

2.RS485和RS232

RS485RS232
传输规范半双工同步协议异步串口协议
工作模式半双工全双工
信号线一对双绞线:A、BRXD、TXD、GND三条线
传输距离1200米15米
传输速率10Mbps20Kbps
电气电平值

逻辑"1"以两线间的电压差为+(2-6)V表示

逻辑"0"以两线间的电压差为 -(2-6)V表示 

负逻辑关系

即:逻辑"1",-(5-15)V;逻辑"0 " +(5- 15)V 

通信能力允许连接多达128个收发器只允许一对一通信。
抗干扰性采用平衡驱动器和差分接收器的组合,抗噪声干扰性好。使用一根信号线和一根信号返回线而构成共地的传输形式,这种共地传输容易产生共模干扰。

五、Linux

1.Linux系统中实现多并发

六、STM32

1.测报灯使用的stm32

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

block by James

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值