本文主要是根据面经自己整理的一些知识点,侵权删!!!!
目录
目录
1.8 strlen("\0")=?0 sizeof("\0")=?2
1.9 struct 与 union 的区别是什么:结构体和联合体区别
1.预编译阶段:gcc -E main.c -o main.i
2.编译阶段:gcc -S main.i -o main.s
3.汇编阶段:gcc -c main.s -o main.o
4.4#include和#include “filename.h”
5.2 全局变量是否可以被定义在多个.c文件包含的头文件中,为什么?
6.5 析构函数可以是virtual 型,构造函数不能是虚函数
10.3 给定一个整形变量a,设置a的bit 3,第二个清除a 的 bit 3,
1、关键字
1.1C语言宏中的#和##
#可以将宏定义中的传入参数名转换为双引号括起来的参数名字符串,必须置于宏定义的参数名前
##是将宏定义的多个形参转换成一个实际的参数名
#define STRCPY(a, b) strcpy(a ## _p, #b)
int main()
{
char var1_p[20];
char var2_p[30];
strcpy(var1_p, "aaaa");
strcpy(var2_p, "bbbb");
STRCPY(var1, var2);//转换为strcpy(var1_p,"var2")
STRCPY(var2, var1);
printf("var1 = %s\n", var1_p);//var1=var2
printf("var2 = %s\n", var2_p);//var2=var1
return 0;
}
1.2关键词volatile的使用场景
volatile(直接存取原始内存地址)(避免对应的变量值在程序不知道的情况下发生改变)
使用场景:
1.并行设备的硬件寄存器(状态寄存器)
2.中断服务子程序会访问的变量
3.多线程应用中被几个任务共享的变量
1.3关键词static的作用
作用:更改函数与变量的作用域,只在内存中分配一次;
1、在函数体内,一个被声明的静态局部变量在函数反复调用时保持值不变
2、模块内,函数体外,一个被声明的全局静态变量只能被模块内函数访问
3、模块内的静态函数可以被模块内的其他函数使用
附加上全局变量、局部变量、静态局部变量、静态全局变量
C++变量根据定义的位置不同,具有不同的作用域,作用域可以分为6种:全局作用域,局部作用域,语句作用域,类作用域,命名作用域,和文件作用域。
全局变量具有全局作用域,全局变量只需要在一个源文件中定义,就可以作用于所有的源文件中。当然,其他不包括全局变量定义的源文件需要用extern关键字再次声明这个全局变量。
静态局部变量具有局部作用域,只被初始化一次,自从第一次初始化一直到程序结束都一直存在,他和全局变量的区别在于全局变量对所有的函数的都是可见,而静态局部变量只对定义自己的函数体始终可见。
局部变量具有局部作用域,属于自动对象,他在程序运行期间不是一直存在的,而只是在函数执行期间存在,函数的一次调用结束以后,变量就被撤销,内存也会被收回
静态全局变量也具有全局作用域,他与全局变量的区别的区别在于如果程序包含多个文件的话,他作用在定义他的文件里面,不能作用到其他文件中,即被static关键字修饰过的变量具有文件作用域,即使两个不同的源文件都定义了相同的静态局部变量,他们也是不同的变量。
从分配内存看
全局变量、静态局部变量和静态全局变量都在静态存储区分配空间,而局部变量在栈分配空间
全局变量本身就是静态存储方式,静态全局变量也是静态存储方式,两者在存储方式上没有多少区别,区别在于非静态全局变量在各个源文件中都是有效的,而静态全局变量则是限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。由于静态全局变量的作用域只限制在一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
1.4extern关键字(声明)
1、变量声明情况分类
int i ;声明+定位 需要存储空间
extern int i; 声明 + 定义 不需要内存空间
2、在C语言中,修饰符extern用在变量和函数的声明之前,用来说明此变量/函数是在别处定位的,需要在此处引用。 extern 的应用方式比包含头文件方便很多,想用哪个函数就extern哪个函数,加速编译过程。
3、extern “C” 指示编译器这部分代码按C语言规则编译。
#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh
#ifdef __cplusplus//告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{
#endif
/*…*/
#ifdef __cplusplus
}
#endif
#endif /*end of __INCvxWorksh*/
1.5 const 作用
1、定义变量为常量,必须初始化
2、修饰函数的参数,表示在函数体内不能修改这个参数的值
3、修饰函数的返回值(只用于修饰指针变量,一般都是修饰内容不变)
4、相比与宏,const可以避免不必要的内存分配,宏在编译时每次替换都会分配内存空间,const只分配一次。
1.6 const使用场景
2、修饰常数组、常指针、常对象(对象常量、不能被更新)
3、修饰形参
4、修饰函数返回值
1.7new/delete和malloc/free的区别
1.new/delete是c++中的操作符,malloc/free是库函数
2.new和delete对应于构造函数和析构函数,,malloc和free是内存分配
3.new返回指定类型的指针,并且可以自动计算申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针
1.8 strlen("\0")=?0 sizeof("\0")=?2
1、strlen是库函数,用于计算字符串长度(),遇到\0结束。sizeof是关键字,以字节形式给出操作数存储大小。
2、strlen在运行期计算,sizeof在编译期计算。
不用sizeof如何求 int 占用的字节数
#include <stdio.h>
#define Mysizeof(Value) (char*)(&Value+1)-(char*)&value
int main()
{
int i;
double f;
double *q;
printf("%d\r\n",Mysizeof(i));
printf("%d\r\n",Mysizeof(f));
printf("%d\r\n",Mysizeof(a));
printf("%d\r\n",Mysizeof(q));
return 0 ;
}
4 8 32 4
(char*)&Value 返回Value的地址的第一个字节,(char*)(&Value+1)返回value 的地址的下一个地址的第一个字节。
不用sizeof求电脑的位数(只需要求两个char指针之间的宽度)
#include <iostream>
using namespace std;
int main() {
void* a;
void* b;
int scope = (char*)&a - (char*)&b;
cout << "&a:" << &a << endl;
cout << "&b:" << &b << endl;
cout << "scope:" << scope << endl;
if(scope==8) {
cout << "64bits" << endl;
} else {
cout << "32bits" << endl;
}
}
1.9 struct 与 union 的区别是什么:结构体和联合体区别
struct 和union是两个不同的复合结构,区别在于:
1、联合体共用一块地址空间,一个联合体变量的长度等于最长的成员长度;结构体不同成员放在不同的地址中,占用空间是累加的(考虑空间对齐);
2、对联合体不同成员赋值将会对他的其他成员重写,原来的成员的值就不存在了。对结构体的不同成员赋值是互不影响的。
1.10左值和右值
左值可写,右值可读;
1.11 短路求值
提前判断(逻辑与/逻辑或)
#include <stdio.h>
int main()
{
int i = 6;
int j = 1;
if(i>0||(j++)>0);//提前判断
printf("%D\r\n",j);
return 0; }
1.12 ++a和a++运算
int temp = a;
a = a+1;
return temp;
++a的过程
a = a + 1;
return a ;
1.13#define和typedef区别
#define是字符串替换,typedef是定义类型;
#define是宏,处理时在编译前阶段
typedef是编译阶段的一部分
1.14C语言是怎么进行函数调用的
大多数的CPU的程序是通过栈来实现函数调用的,栈是被用于传递函数参数,存储返回信息,临时保存寄存器原有的值以备恢复以及用来存储局部变量的
函数调用操作所使用的栈部分叫做栈帧结构,每个函数调用都有自己的栈帧结构,栈帧结构由两个指针指定,帧指针(指向起始),栈指针(指向栈顶),函数对于大多数数据的访问都是基于栈指针的。
2.内存
2.1C语言内存分配的方式
栈:函数调用和使用,局部变量;
堆:内存的动态申请和归还 malloc/free
静态数据区:保存全局变量和静态变量
2.2堆和栈区别
1、申请方式(栈上OS自动分配/释放 堆上手动)
2、申请大小的限制(栈小,堆大)
3、地址的生长方向(栈溢出可能会导致栈顶的数据发生覆盖)
4、申请效率(栈快 , 堆慢)
2.3栈的用处
1、存储临时变量和函数参数
2、多线程编程的基石,每个线程都有专属的栈,中断和异常处理也有栈;
2.4 压栈顺序
从右往左
int p;
printf("%d %d %d\n",p++,++p,p++)
2 2 0
2.4 C++内存管理
C++中的虚拟内存分为 代码段,数据段,BSS段,堆区,文件映射区,以及栈区;
代码段:包括只读存储区(存储字符串常量)和文本区(机器代码)
数据段:存储程序中已经初始化的全局变量和静态变量
BSS段:存储未初始化的全局变量和所有被初始化为0的全局变量和静态变量
堆区:调用new/malloc函数在堆区动态分配内存,调用delete和free释放内存
映射区:存储动态链接库
栈:存储函数的返回地址,参数,局部变量,返回值
2.5内存泄漏
内存泄漏就是指申请了一块内存,但是使用完以后没有释放掉
3.指针
3.1数组指针和指针数组
数组指针是指向数组的指针
//声明了一个指针,该指针指向了一个有9个int型元素的数组
int (*pa)[9]
指针数组是指一个数组,数组中的元素是指针(其实就是相当于数组元素是地址,解引用之后可以得到地址中的值)
3.2函数指针和指针函数
函数指针是一个指针,该指针指向一个函数
int(*p)(int, int);
# include <stdio.h>
int Max(int, int); //函数声明
int main(void) {
int(*p)(int, int); //定义一个函数指针
int a, b, c;
p = Max; //把函数Max赋给指针变量p, 使p指向Max函数
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = (*p)(a, b); //通过函数指针调用Max函数
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0; }
int Max(int x, int y) //定义Max函数
{
int z;
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
指针函数是指返回值为指针类型(地址值)的函数
int *pfun(int, int);
#include <stdio.h>
float *find(float(*pionter)[4],int n);//函数声明
int main(void)
{
static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};
float *p;
int i,m;
printf("Enter the number to be found:");
scanf("%d",&m);
printf("the score of NO.%d are:\n",m);
p=find(score,m-1);
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i));
return 0;
}
float *find(float(*pionter)[4],int n)/*定义指针函数*/
{
float *pt;
pt=*(pionter+n);
return(pt);
}
3.3数组名与指针
- 1.数据保存方面
- 指针保存的是地址,内存访问偏移量为四个字节,无论其中保存的是何种数据,都是以地址类型进行解析
- 数组保存的数据,数组名表示的是第一个元素的地址,内存偏移量是保存数据类型的内存偏移量,只有对数组名取地址时,数组名才表示整个数组, 内存访问偏移量为整个数组的大小(sizeof(数组名))
- 2.数据访问方面
- 指针是间接访问,使用解引用,*
- 数组对数据访问是直接访问,通过下标或者数组名加偏移量
- 3.使用环境
- 指针多用于动态数据结构,(链表等)和动态内存开辟
- 数组用于存储固定个数且类型统一的数据结构和隐式分配
3.4指针常量 常量指针,指向常量的常量指针
常量指针:
int const *p = &a;
const int *p = &a;
指针指向的是个常量,指针指向的值就不能被改变
理解如下:
指针常量:指针本身是常量,指向一个确定存在的地方。
int * const p;
int a = 0, b = 0;
int *const p = &a;
*p = 1;//正确,可以改初值
*p = &b;//错误,不可改地址
指向常量的指针常量(地址不可变,值不可变)
int a = 0, b = 0;
const int *const p = &a;
*p = 1;//不可以修改初值
*p = &b;//不可以改地址
3.5指针和引用区别
指针:保存另一个变量的内存地址,通过*访问保存的内存地址中的值
引用:相当于另一个变量的别名,一旦初始化就不能改变
区别:
1、初始化不同:指针可以先定义在初始化,可以重复赋值,但是引用在定义时就必须先初始化,一旦初始化就不能改变;
2、赋值NULL
指针可以赋值为NULL,引用不可以
3、指针可以多级,但是引用不可以;
同:都是地址的概念,从内存分配上来看,两者都是占用内存的
不同:
1.指针是实体,引用是别名
2.引用只能初始化一次,不能为空,之后不可变,指针可变可空
3.sizeof得到的引用是指向的变量(对象)的大小,指针是指针本身的大小
转换
指针转引用:把指针用*就可以转换成对象,可以用在引用参数中
引用转指针:把引用类型的对象用&取地址就获得指针
int a = 5;
int *p = &a;
void fun (int &x){}
3.6野指针
1.野指针指向不可用内存,创建时没有初始化会随机指向
2.free或delete时,没有将指针指向null,因为只释放了内存
3.指针超过了变量的作用范围
避免:
- 初始化,使用完进行null赋值,
- malloc函数分配完内存后需注意:
- a. 检查是否分配成功(若分配成功,返回内存的首地址;分配不成功,返回NULL。可以通过if语句来判断)
- b. 清空内存中的数据(malloc分配的空间里可能存在垃圾值,用memset或bzero 函数清空内存)
3.7C++中智能指针
智能指针是指向动态对象的指针类,方便管理堆内存,使用普通指针,容易造成堆内存泄漏,二次释放,其中主要有,
unique_ptr C++11引入用来替代auto_ptr,解决不安全问题(auto有拷贝语义,再次访问原对象,会程序崩溃,而unique提供了移动语义)不共享所管理的对象,
shared_ptr 是共享指针,允许多个指针指向同一个对象,除了包括指向对象的指针,还必须包括一个引用计数代理对象的指针
weak_ptr 是配合shared_ptr使用的,观测资源的引用计数,可以用于打破循环引用(比如两个类互相引用),解决内存泄漏的问题
weak_ptr不会增加引用计数,是不能直接访问对象的,访问可以通过lock()函数来创建shared_ptr来引用,shared_ptr能够保证在 shared_ptr 没有被释放之前,其所管理的资源是不会被释放的
4 预处理
预处理阶段都进行了哪些操作呢?
1、对所有以 # 开头的语句进行处理,其中包括我们熟知的:#define、#include <xxx.h>、条件编译指令#ifdef等。
2、删除所有的“/**/”和“//”注释。
3、添加行号和文件名标识,方便编译器产生的调用以及当出现编译错误或者是警告时可以显示行号。
4、保留所有的 #pragma 编译指令
使用 -S选项,编译器执行完编译阶段就结束,最后形成 .s 文件,编译阶段是整个程序从C到机器语言翻译过程的核心
使用 -c 选项,编译器执行完汇编阶段就结束,形成 .o (windows下为 .obj ) 对象文件。
其中汇编器将会汇编代码转换为机器可识别的机器代码
前一个阶段我们得到了若干个对象文件,现在我们要做的就是将这几个对象文件链接起来,形成最后的可执行文件。
(这其中还涉及到静态链接库和动态链接库的概念)
main.c–>main需要四步:预处理,编译,汇编,连接
1.预编译阶段:gcc -E main.c -o main.i
- 预编译阶段主要做六件事:
- (1)删除#define,并做文本替换;
- (2)递归展开头文件;
- (3)处理以#开头的预编译指令;
- (4)删除注释部分;
- (5)添加行号和文件标识;
- (6)保留#pragma指令,供给编译器,
- 预编译阶段的语言还属于高级语言,计算机不能理解.
2.编译阶段:gcc -S main.i -o main.s
- 编译阶段主要有五件事:
- (1)进行词法分析;
- (2)语法分析;
- (3)语义分析;
- (4)代码优化;
- (5)生成汇编指令
- 编译阶段的语言属于低级语言.
3.汇编阶段:gcc -c main.s -o main.o
汇编阶段主要是翻译指令(即将低级语言翻译成机器语言).
4.链接阶段:gcc -o main.o main
链接阶段主要做汇编阶段未做的事情:
(1)强弱符号的处理;
(2)外部符号的处理;
(3)指令段中虚假地址和虚假偏移的处理;
(4)符号的重定位
4.1 预处理标识符#error
#error预处理指令作用是在编译程序时,只要遇到#error就会生成一个编译错误提示信息,并且停止编译。语法格式为#error error-message。
4.2define和const
define是做文本替换,
1.内存区域不同:define常量的生命周期在编译期,不分配内存,存在于代码段,const常量存在于数据段,并在堆栈中分配空间,可以被调用传递
2.数据类型不同,define常量没有数据类型,const实际存在,并且可以编译器检查
3.define替换方便,但容易出错,const可读性强,便于维护和调试。
4.3typedef 和define区别
两者都是替一个对象取别名,区别在于
1、原理不同;#define是预处理指令,在预处理阶段做简单的字符串替换,没有正确性检查
typedef是关键字,在编译时处理。有正确性检查
#define Pi 3.1415926
typedef int INTEGER
INTEGER a,b;
2、功能不同 typedef用于定义类型的别名
#define不只是可以为对象取别名,还可以定义常量/变量,编译开关等
3、作用域不同 #define没有作用域的限制,之前定义过的之后都可以使用
typedef有自己的定义域
4、对指针的操作不同 #define是没有任何意义的替换,仅为字符串替换
typedef有一定含义
#define INTPTR1 int*
typedef int* INTPTR2;
INTPTR1 p1,p2; -> int* P1,p2;
INTPTR2 p3,p4; -> int* P3,*p4;
4.4#include<filename.h>和#include “filename.h”
#include<filename.h>是从标准库路径开始搜索
#include“filename.h”是先从工作路径开始搜索,再从标准库路径开始搜索
4.5头文件作用
1、调用库功能
2、检查类型安全(报错)
4.6 头文件中是否可以定义静态变量
头文件中尽量不要定义变量,会造成空间浪费和程序错误
宏输入两个参数并返回较小的一个
#define MIN(A,B) ((A)<=(B)?(A):(B))
4.7静态链接和动态链接的联系与区别
动态库
1、链接时不复制,程序运行由系统动态加载到内存中,只加载一次;
2、加载速度比静态链接慢
3、发布程序需要提供依赖的动态库
静态库
1、静态库被打包在程序中,加载速度快
2、程序发布无需提供静态库,因为静态库已经包含在程序中
3、更新,发布麻烦,存在冗余拷贝;
5、变量
5.1全局变量和局部变量区别
1、作用域不同
2、存储方式不同:全局变量(静态全局变量,静态局部变量)分配在全局数据区(静态存储区),后者分配在栈区
3、生命周期不同
4、使用方式不同(全局变量extern就行)
5.2 全局变量是否可以被定义在多个.c文件包含的头文件中,为什么?
可以,在不同的C文件中以static形式来声明同名全局变量
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件对此变量赋初值
5.3局部变量是否可以和全局变量重名
能,局部会屏蔽全局
在函数内部引用这个变量时,会用局部变量
5.4链表和数组的区别
链表通过指针来链接元素与元素,而数组是把所有元素依次排序;
链表分为:单向链表,双向链表,循环链表
链表是链式存储结构,数组是顺序存储结构;
总结:
数组便于查询和修改,但是不便于新增和删除
链表适合新增和删除,但是不适合查询;
6、函数
6.1怎么可以让函数在main函数之前运行
attribute可以设置函数属性(FUnction Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)
#include <stdio.h>
void before() _attribute_((constructor));
void after() _attribute_((destructor));
void before(){
printf("this is function %s\n",_func_);
return;
}
void after(){
printf("this is function %s\n",_func_);
return;
}
int main(){
printf("this is function %s\n",_func_);
return 0;
}
//输出结果
//this is function before
//this is function main
//this is function after
6.2为什么析构函数必须是虚函数
当我们new一个子类时,可以用基类指针指向该子类对象,释放基类指针时可以释放该子类的空间,防止内存泄漏。
6.3 为什么默认的析构函数不是虚函数?
因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存,对于不会被继承的类来说,析构函数如果是虚函数就会浪费内存。
6.4 C++中析构函数的作用:
析构函数允许类自动完成清理工作,不需要调用其他的成员函数;
6.5 析构函数可以是virtual 型,构造函数不能是虚函数
虚函数的主要意思在于子类继承从而产生多态,子类的构造函数中,编译器会加入基类的代码,如果基类的构造函数用到了参数,那子类在构造函数的初始化中就必须为基类给参数。虚函数就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法,在构造函数运行时,对象的动态类型还不完整,没有确定类型,所以构造函数不能动态绑定;
6.5 C++静态函数和虚函数的区别
静态函数在编译时就确定了运行时机,虚函数需要在运行时动态绑定,虚函数运用了虚函数表机制,调用时会增加一次内存开销。
6.3 函数的默认参数
在C++中,函数的形参列表中的形参是有默认值的
1、如果某个位置参数有默认值,那么从这个位置往后,从左往右都必须要有默认值
2、如果函数声明有默认值,函数实现的时候就不能有默认值
6.4函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时就必须填补该位置;
6.5函数重载
函数名可以相同,提高复用性
函数重载的满足条件
1、在同一个作用域下
2、函数名称相同
3、函数的参数类型不同或者个数不同或者顺序不同
函数重载的注意事项
1、引用作为函数重载参数
2、函数重载碰到默认参数
6.6 虚函数表具体是怎么样实现多态的
原理:
虚函数表是一个类的虚函数的地址表,对象在创建时,会有一个指针指向该类的虚函数表,当子类对象重写虚函数时,父类的虚函数表中的对应的位姿会被子类的虚函数地址覆盖;
作用:
在用父类的指针调用子类对象成员时,虚函数表会指明要调用的具体函数是哪个。
6.7 C语言是怎么进行函数调用的
CPU支持使用栈来支持函数调用操作,栈被用于传递函数参数,存储返回信息,临时保存寄存器的原有值,栈指针(指向起始),栈指针(指向栈顶);
6.6 类和对象
类:将规律抽象出来为类
对象:具有相同相同性质的为对象
6.7封装、继承,多态
封装:
意义:
1、将属性和行为作为一个整体,表现生活中的事务
2、将属性和行为加以权限控制
class 类名{ 访问权限: 属性 /行为 };
访问权限有三种:
1、public 公共权限 类内可以访问,类外可以访问
2、protected 保护权限 ;类内可以访问,类外不可以访问
3、private 私有权限 ;类内可以访问,类外不可以访问
继承:
允许和鼓励类的重用,对象的新类可以从现有类中派生(这个过程就是派生),新类继承了原始类的特性。
多态:
允许不同类的对象对同一消息做出响应
6.8 什么是深拷贝,什么是浅拷贝
深拷贝是彻底的拷贝,两个对象中的所有成员都是独一份的
浅拷贝就是某一些成员变量可能是共享的
6.9 什么是友元
在类的成员函数外部直接访问类的私有成员;
- 友元函数:普通函数对一个访问某个类中的私有或者保护成员
- 友元类:类A中的成员函数访问类B中的私有或保护成员
struct 和 class区别:默认的访问权限不同
struct默认权限为公共
class默认权限为私有
构造函数和析构函数
构造函数:主要作用在于创建对象时为对象的成员的属性赋值,构造函数由编译器自动调用,无需手动调用
1、没有返回值,不用写void;
2、函数名与类名相同
3、构造函数可以有参数,可以发生重载
4、创建对象的时候,构造函数会自动调用,而且只调用一次
析构函数:主要作用就是在对象销毁前系统自动调用,执行一些清理工作
1、没有返回值,不写void
2、函数名和类名相同,在名称之前加 ~
3、析构函数不可以有参数,不可以发生重载
4、对象在销毁前会自动调用析构函数,而且指挥调用一次
构造函数的分类和调用
两种分类方式:
1、有无参数可以分为:有参构造和无参构造
2、按照类型分:普通构造和拷贝构造
Person p1;
Person p2(10);
Person p3(p2); //没有左值的话,在当前行执行结束以后会直接回收匿名对象;
不要用拷贝构造函数来初始化一个匿名对象,因为这个时候会认为这是对象的声明,也就是会对同一个变量重定义;比如:
Person p3 = Person (p2)//这就是一个拷贝构造
Person (p3)//此时就会发生重定义;
调用方式:
1、括号法
2、显示法
3、隐式转换法
7、ARM架构
7.1、CPU、MPU、MCU、SOC、SPOC区别
CPU 是指运算和控制核心,CPU由运算器、控制器、寄存器和总线组成,CPU的运作原理可以分为四个阶段,提取,解码,执行,写回;CPU从存储器或高速缓冲存储器中提取指令,放入指令寄存器解码,并执行指令;
MPU是微处理器,通常代表一个功能强大的CPU
MCU是指微控制器,是由CPU和RAM,ROM,定时器,计数器和多个IO接口集成在一个芯片上,比如51
MCU和MPU的区别在于能否直接执行代码,MCU可以,MPU需要外接RAM、ROM;
SOC是指片上系统,也就是系统级芯片,可以运行操作系统
SPOC是指硬件配置和软件配置都可以进行修改的芯片,就像FPGA
7.2、交叉编译
在一种计算机环境中能够运行的编译程序,也能够在另一种环境下运行的代码;
交叉编译:
在一个平台上生成另一个平台上的可执行代码;
为什么需要交叉编译
因为有一些目的平台上不允许或者不能安装我们需要的编译器,而我们又需要这个编译器的编译特征;有时是因为目的平台上资源匮乏,无法运行我们需要的编译器等;
7.3、嵌入式基于ROM和基于RAM的运行方式的区别
RAM:随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器。掉电消失
ROM:(只读内存(Read-Only Memory)简称)英文简称ROM。ROM所存数据,一般是装入整机前事先写好的,整机工作过程中只能读出,而不像随机存储器那样能快速地、方便地加以改写。掉电不消失。
基于RAM:
1、需要先把硬盘和其他介质的代码先加载到ram中,加载过程一般有重定位的操作
2、速度比基于ROM快,可用的RAM比ROM少,所有的代码和数据必须放在RAM中;
基于ROM
1、速度比较慢,因为会有一个把变量、部分代码从存储器(硬盘、flash)搬移到RAM的过程
2、可用的资源比RAM多;
8、ARM处理器
8.1、什么是哈弗结构和冯诺依曼结构
定义:
冯诺依曼结构是指采用指令和数据统一编址,使用同条总线传输,CPU读取指令和数据的操作没法重叠
哈佛结构是采用指令和数据独立编址,使用两条独立的总线传输,CPU读取指令和数据的操作可以同时进行
利弊
冯诺依曼结构主要用在通用计算机领域,需要对存储器中的代码和数据频繁的进行修改,统一编址有利于节约资源;
哈佛结构主要用于嵌入式计算机,程序固化在硬件中,有较高的可靠性、运算速度和较大的吞吐
8.2、什么是arm流水线技术
流水线技术就是通过多个功能部件并行工作来缩短程序执行时间,提高处理器核的运算效率和吞吐量(就是相当于取指(指令),2、译码(识别将要被执行的指令)3、执行(处理指令并将结果写回寄存器));
8.3、arm有多少32位寄存器
ARM处理器有37个寄存器,它包含31个通用寄存器和6个状态寄存器
8.4、ARM指令集分为几类
2类,Thumb指令集和ARM指令集,ARM指令长度为32位,Thmub指令集长度为16位;所以使得arm处理器可以执行16位指令又能执行32位指令,从而增强了ARM内核功能
8.5、ARM处理器有几种工作状态
一般有ARM和Thmub两种状态,并且可以在两者之间相互切换
1、ARM状态:此时处理器执行32位对齐ARM指令,大部分工作在此状态
2、Thmub状态:此时处理器执行16位的半字对齐的Thmub指令;
8.6、ARM体系中函数调用时的参数是怎么传递的?
当参数小于等于4的时候通过r0-r3寄存器来进行传递的,当参数大于4的时候,通过压栈的方式进行传递;
8.7、锁相环
功能就是产生分频倍频时钟
8.8、中断和异常的区别
中断是指外部硬件产生一个电信号从CPU中断引脚进入,打断CPU运行;
异常是指软件运行过程中发生一些必须要处理的事情,CPU自动产生一个陷入来打断CPU运行。异常在处理的时候必须考虑到处理器的时钟同步,比如在处理器执行到因编译错误而导致的错误指令时,处理器就给一个异常;;
8.9、中断和DMA有什么区别?
DMA:是一种无须CPU参与的,就可以让外设和内存之间进行数据双向传递的硬件机制,使用DMA乐意不借助CPU就实现数据传输,增大系统的吞吐量
中断:是指在CPU执行程序过程中,出现了某一些突发事件,CPU必须暂停当前执行的程序,转去处理突发事件,处理完毕以后,CPU又返回源程序被中断的位置并继续执行
所以DMA和中断最主要的区别就是DMA不需要CPU参与,但是中断是需要CPU参与的。
8.10、中断的响应执行流程是什么
中断的响应流程:CPU接受中断->保护中断上下文跳转中断处理程序->执行中断处理程序->恢复中断现场。
8.11、中断和轮询
中断是指CPU在被动状态下接受设备的信号,而轮询则是CPU主动去查询该设备是否有请求;
8.12、同步传输和异步传输
同步传输是指,需要外界的时钟信号,把数据字节组合起来一起发送,速度快
异步传输是指,数据每次按照一个字节进行传输,速度慢
8.13、RS232和RS485
1、传输的距离不同,485远,232近(20m)
2、设备数量:485多,232少(一对一)
3、连接方式:485用差分电平表示数据,要两根线才能满足基本要求,全双工则需要4根线
232用电平表示数据,两根线就可以实现全双工
8.14、常用的GCC指令
1、预处理
gcc -E test.c -o test.i //把预处理的结果导出到test.i文件中
2、编译
gcc -S test.c -o test.s //编译器将test.i翻译成汇编语言,将结果保存在test.s中
3、汇编
gcc -c test.s -o test.o //将汇编代码编译为目标文件,但不链接
4、链接
gcc test.o -o test //将生成的目标文件test.o生成最终的可执行文件test
一步编译到位
gcc test.c -o test
多文件编译
gcc test1.c test2.c -o test
9、中断
9.1、硬中断和软中断的区别
硬中断:
1、硬中断是由硬件产生的,每个设备都有自己的中断请求
2、硬中断可以直接中断CPU
软中断
1、软中断是由当前正在运行的进程产生的
2、软中断通常是对一些IO的请求,这些请求会调用内核中可以调度IO发生的程序,对于某些设备,IO请求需要被立即处理
3、软中断并不会直接中断CPU,只有当前正在运行的代码才会产生软中断
区别:
1、软中断是执行中断指令产生的,硬中断是外设引发的
2、软中断的中断号是由指令直接发出的,无需中断控制器控制,硬中断的中断号是由中断控制器提供的
3、硬中断是可以屏蔽的,软中断不可以屏蔽
4、硬中断处理程序要保证可以快速的完成任务,这样程序的执行才不会等待太长时间,称为上半部
5、软中断处理中断未完成的工作,属于下半部;
9.2、中断为什么要分上半部和下半部
Linux中断分为硬件中断和异常(内部中断),调用过程为,外部中断产生->发送中断信号到中断控制器->通知处理器产生中断的中断号;
将中断处理一分为二,上半部登记新的中断,快速的处理简单的任务,剩下耗时的处理留给下半部处理,下半部处理可以被中断,上半部处理不可以被中断;
10 位操作
10.1 求解整数型的二进制表示中1的个数
#include <stdio.h>
int func(int x)
{
int count = 0;
while(x)
{
count++;
x = x&(x-1);
}
return count;
}
10.2 变换两个变量的值,不使用第三个变量
a = a + b;
b = a - b;
a = a - b;
10.3 给定一个整形变量a,设置a的bit 3,第二个清除a 的 bit 3,
#define BIT3 (0x1<<3)
static int a;
void set_bit(void)
{
a|= BIT3; //或
}
void clear_bit(void)
{
a &= ~BIT3;
}
11 虚函数
11.1 什么是虚函数
指向基类的指针在操作多态对象时,可以根据不同类对象调用相对应的函数
虚函数的作用:在基类定义了虚函数后,可以在派生类中对虚函数进行从新定义,并且可以根据基类指针和应用,在程序运行阶段动态调用基类和不同派生类的同名函数;
11.2 C++如何实现多态
通过虚函数实现多态,虚函数的本质是通过基类指针访问派生类定义的函数。
每个含有虚函数的类,实例对象内部都有一个虚函数表的指针,该虚函数指针被初始化为类的虚函数表的内存地址,在程序中,不管对象如何转换,该对象内部的虚函数表指针都是固定的。
11.3 纯虚函数
基类中不能对虚函数给出有意义的实现,只能把函数的实现留给派生类对象;
含有纯虚函数的类称为抽象类,抽象类不能生成对象,纯虚函数永远不会被调用,用于同一管理子类对象