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<<" ";
}
定义一个5行6列的空二维数组并输出
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关键字,并加以扩充。
- c语言中,struct只能包含成员变量,不能包含成员函数。c++中,struct类似class,既可以包含成员变量,又可以包含成员函数。
- 使用class,类中的成员默认是private属性;使用struct,结构体中的成员默认是public属性。
- class继承,默认是private继承;struct继承,默认是public继承。
- 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()。
以上是关于C++ 面试准备(待续)的主要内容,如果未能解决你的问题,请参考以下文章