C++ 面试准备

Posted KAI-yq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 面试准备相关的知识,希望对你有一定的参考价值。

目录

反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

迭代

class Solution 
public:
    ListNode* reverseList(ListNode* head) 
        ListNode *prev = nullptr , *curr = head , *next ; 
        while(curr != nullptr)
            next = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next;
        
    return prev;
    
;

递归

class Solution 
public:
    ListNode* reverseList(ListNode* head) 
        if(head == nullptr || head->next == nullptr)
            return head;
        
        ListNode* p = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return p;
    
;

注意点:
链表定义加* , 如ListNode* p;
指向下一个节点用 ->
空指针是 nullptr

vector

1.封装动态大小数组的容器,内存空间只增长,不减小
2.头文件 #include
3.构造函数

vector<int>g; vector<int>& g; vector<int>g();//创建空vector

vector<T>(n);//元素个数为n
vector<T>(n,val);//n个元素,值均为val

int arr[5]=1,2,3,4,5
vector<int>g(arr,&arr[5])//arr也可写成&arr[0],begin/end的形式
//&arr[0]=1,&arr[5]=5;

4.用法

g.push_back();//在最后添加数据
g.pop_back();//在最后删除数据

g.size();//g的实际元素个数

g.resize(n);//改变size,
//n小于当前size,删除元素,不改变capacity和内存
//n大于当前size,小于capacity,增加元素,不改变内存
//n大于capacity,增加元素和内存
g.reserve()//改变capacity,改变内存,只能比当前大
//原有capacity为50,增加到第51,则capacity为100

g.clear();//清空元素,不改变capacity

sort(g.begin(),g.end());//从小到大排序,begin得到数组头的指针,end得到数组的最后一个单元+1的指针
//sort需要头文件#include<algorithm>
sort降序需要重写sort
bool compare(int a , int b)
return a>b;

sort(g.begin(),g.end(),compare)

reverse(g.begin(),g.end());//反转
int arr[5]=1,2,3,4,5
reverse(&arr[0],&arr[2]);//输出21345,&arr[0]=0,&arr[2]=2

数组访问
for(int i=0 ; i<g.size() ; i++)
cout<<g[i]<<endl;

迭代器访问
vector<int>::iterator it;//声明一个迭代器
for(it=g.begin() ; it!=g.end() ; it++)
cout<<*it<<" ";


定义一个56列的空二维数组并输出
int N=5 , M=6;
vector<vector<int>>g(N);
for(int i=0 ; i<g.size() ; i++)
g[i].resize(M);

for(int i=0 ; i<g.size() ; i++)
	for(int j=0 ; j<g[i].size() ; j++)
		cout<<g[i][j]<<" ";
	
	cout<<"\\n";


int N=5 , M=6;
vector<vector<int>>g(N,vector<int>(M))
for(int i=0 ; i<g.size() ; i++)
	for(int j=0 ; j<g[i].size() ; j++)
		cout<<g[i][j]<<" ";
	
	cout<<"\\n";


累加求和accumulate,需要头文件#include<numeric>数值计算库
int sum = accumulate(g.begin(),g.end(),42);//42是初始值
string s = accumulate(g.begin(),g.end(),string(" "))// " "是初始值

g.erase(g.begin()+2);//删除第三个元素
g.erase(g.begin(),g.begin()+2)///删除前三个元素
vector<int>::iterator it;
for(it=g.begin() ; it!=g.end())
	g.erase(it);
//erase之后,迭代器it自动指向下一个元素

g.empty();//判断g是否为空,返回true或false

swap(g[0],g[4]);//交换两元素位置
交换两个vector
vector<int>g;
vector<int>f;
g.swap(f);//也可释放内存
另一种释放内存的写法
vector<int>().swap(g);

g.insert(g.begin(),8);//在最前面插入8
g.insert(g.end(),4,1);//在最后插入4个1

vector<int> a;
vector<int> b;
a.assign(b.begin(),b.end())
函数原型:void assign(const_iterator first, const_iterator last);//拷贝函数,把first到last的值赋给a;
assign释放了原本的数据空间,分配了新的数据空间

static

C++内存分布:
1.栈区:编译器自动分配释放,函数结束,空间释放。
2.堆区:程序员分配释放,malloc(c),new(c++)
3.全局数据区(静态区):初始化的全局变量和静态变量在一个区域,未初始化的全局变量和静态变量在相邻的另一块区域
4.代码区

引入:函数内部定义变量,程序执行到定义处,编译器在栈上为变量分配空间,函数执行结束,空间释放。要将此变量保存到下一次调用,如何实现?
1.全局变量(缺点:此函数中定义的变量,不仅受此函数控制)
2.静态变量(静态变量的引入告知编译器,变量存储在程序的静态存储区而非栈上空间。)

static修饰局部变量,表明该变量的值不会因为函数终止而丢失。
static修饰全局变量,表明此变量的值可以被同一文件内的所有函数调用。
static修饰函数,表明该函数只能在同一文件内调用。

static修饰类内成员,表明该成员归此类的所有对象共有,仅有这一个实例。
static修饰不访问非静态成员的类成员函数,表明该静态成员函数只能访问它的参数,类内静态成员,全局变量。

1.在第一次进入note1时初始化,再掉用point不会初始化,直接跳过
如果没有显式初始化,则初始化为0
始终存在,程序结束后释放,作用于此函数
值不变,直到下次改变

int point()
	static int i = 1; // note:1
	//int i = 1;  // note:2
	i += 1;
	return i;

2.全区变量作用于整个工程,static修饰的全局变量只作用于本文件

a.cpp
int  k = 5;
static int q = 6;
b.cpp
extern int k;//可以调用
b.cpp
#include<a.cpp>
static int q;

3.静态函数和静态全局变量用法一样

extern void fn();

4.静态数据成员是每个class(类)有一份,普通数据成员是每个instance(实例)有一份

int Rectangle::s_sum = 0;  //初始化

结论1:不能通过类名调用非静态成员函数,可以通过类名调用静态成员函数,可以通过对象调用类内静态成员函数和非静态成员函数

class Point
public:
	void init()
	
	static void output()
	
;
	void main()
	Point::init();
	Point::output();
	//错误,不能通过类名调用非静态成员函数;
	void main()
	Point pt;
	pt.init();
	pt.output();
	//正确,可以通过对象调用类内静态成员函数和非静态成员函数


结论2:在静态成员函数中不能引入非静态成员

class Point
public:
	void init()
	
	static void output()
		cout<<m_x;
	
private:
	int m_x;
;
	void main()
	Point pt;
	pt.output();//错误,静态成员函数不能调用非静态成员

结论3:非静态成员函数可以调用静态成员函数,反之不能

class Point
public:
	void init()
	output();

	static void output()
	
;
	void main()
	Point pt;
	pt.init();

核心思想:静态成员是类初始化的时候产生,非静态成员是类实例化的时候产生,初始化早于实例化。

虚函数和纯虚函数

定义一个函数为虚函数,不代表此函数为不被实现的函数,允许用基类的指针调用子类这个函数;
定义一个函数为纯虚函数,代表此函数不被实现,规范继承这个类的程序员必须实现这个函数;

虚函数:

class A
public:
	virtual void foo()
	cout<<A<<endl;
	
;
class B:public A
public:
	void foo()
	cout<<B<<endl;
	
;
int main(void)
	A *a=new B();
	a->foo();//调用的B里的foo
	return 0;

纯虚函数:在基类中定义的虚函数,没有实现,要求子类都要实现
在继承类中必须重新声明

virtual void foo()=0;//纯虚函数的定义方法

带有纯虚函数的类为抽象类

const指针

const int *p//指向整型常量的指针
int *const p//指向整型的常指针(指针不能指向别的)

unique_ptr和shared_ptr的区别

1.unique_ptr:专属所有权
unique_ptr管理的内存,只能被一个对象持有,因此,unique_ptr不支持复制和赋值:

auto w = std::unique_ptr<Weight>(new Weight());
auto w2 = w;//编译错误

因为,复制是两个对象共享一块内存,unique_ptr只支持移动:

auto w = std::unique_ptr<Weight>(new Weight());
auto w2 = std::move(w);//w2获得内存所有权,w此时等于nullptr不再持有此内存

性能:unique_ptr在默认情况下和裸指针大小一样,内存无额外消耗,性能最优。

使用场景:
1.忘记delete:

class Box 
public:
  Box() : w(new Weight()) 
    
  
  ~Box() 
    //忘记delete w
  
private:
  Weight* w;
;

w建立在堆上,用裸指针管理w,就必须在析构函数中delete w;,忘记写delete,造成内存泄漏。
如果用unique_ptr管理内存,就不需要手动delete,当对象析构,w自动释放内存。
2.异常安全:

void process() 
  Weight* w = new Weight();
  w->do_something();//可能会发生异常
  delete w;//无法delete w

如果w->do_something();发生异常,delete w;将不会被执行,此时会内存泄漏。
可以使用try…catch捕捉异常,在catch里执行delete,这样代码不美观,也容易忘写。
使用unique_ptr,无论代码怎么抛异常,在unique_ptr离开函数作用域时,内存自动释放。

2.shared_ptr:共享所有权
多个shared_ptr可以共享一块内存,shared_ptr支持复制。

auto w = std::make_shared<Weight>(new Weight);

  auto w2 = w;
  cout << w.use_count() << endl;// 2

cout << w.use_count() << endl;// 1

每复制一个shared_ptr,引用计数+1,当shared_ptr离开作用域,引用计数-1,当引用计数为0,delete内存。
shared_ptr也支持移动,用法与unique_ptr相同。
shared_ptr内存占用高,要多维护一个引用计数。

struct和class的区别

c++保留了c语言的struct关键字,并加以扩充。

  1. c语言中,struct只能包含成员变量,不能包含成员函数。c++中,struct类似class,既可以包含成员变量,又可以包含成员函数。
  2. 使用class,类中的成员默认是private属性;使用struct,结构体中的成员默认是public属性。
  3. class继承,默认是private继承;struct继承,默认是public继承。
  4. class可以使用模板,struct不能使用模板。template<typename T>
    编写代码时,更倾向与用class定义类,struct定义结构体,这样语义更明确。

emplace_back和push_back的区别

emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
  显然完成同样的操作,push_back() 的底层实现过程比 emplace_back() 更繁琐,换句话说,emplace_back() 的执行效率比 push_back() 高。因此,在实际使用时,建议优先选用 emplace_back()。
   由于 emplace_back() 是 C++ 11 标准新增加的,如果程序要兼顾之前的版本,还是应该使用 push_back()。

内联函数

如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。如果有修改,全部要重新编译。
副本即:编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换。用空间换时间,提高调用效率。

1.在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。
2.在内联函数内不允许使用循环语句和开关语句。
3.内联函数最好是1-5行的小函数。

malloc和new

new能将对象初始化,malloc不能

int a=new int;    //分配int类型所需长度(即sizeof(int))的内存空间,且返回int*指针。
 
int b=(int*)malloc(sizeof(int));    //(同),但返回为void*,必须强制转换,否则会报错。
                                    //并且所分配的内存大小自定
                                    
new运算符可以分配一串内存(用来存数组),实际上,malloc()也可以:
int a[100]=new int[100];

int a[100]=(int*)malloc(sizeof(int)*100);//100个sizeof(int)的大小

newdelete ,delete a;
malloc用free,如 free(b);

以上是关于C++ 面试准备的主要内容,如果未能解决你的问题,请参考以下文章

自己准备的C++方面面试题

C++面试应该准备哪些技能点?分别能达到什么薪资水平?

别再问我怎么准备C++面试了!

C++ 面试准备

C++ 面试准备

C++ 面试准备(待续)