面试题整理

10 篇文章 3 订阅

C++一个类会默认创建的函数

在C++中,一个类有八个默认函数:
1)默认构造函数;
2)默认拷贝构造函数;
3)默认析构函数;
4)默认重载赋值运算符函数;
5)默认重载取址运算符函数;
6)默认重载取址运算符const函数;
7)默认移动构造函数(C++11);
8)默认重载移动赋值操作符函数(C++11);

什么时候需要调用拷贝构造函数,什么时候需要重写拷贝构造函数

当用一个已初始化完成的对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用
1)新建对象
2)函数的参数为类的对象
3)函数的返回值为类的对象

当构造函数涉及到动态存储分配空间时,要⾃⼰写拷贝构造函数,并且要深拷贝

拷贝构造不传引用会怎么样

如果拷贝构造函数参数不使用引用传递而是采用值传递方式,会导致无穷递归调用,最终导致栈溢出错误

浅拷贝和深拷贝

1)浅拷贝: 浅拷贝是指在进行对象拷贝时,只复制对象中的成员变量的值,而不复制指向动态分配内存的指针。这意味着原始对象和拷贝对象将共享相同的内存资源。当一个对象的析构函数被调用时,可能会导致两个对象同时释放同一块内存,这样会导致内存错误;
2)深拷贝: 深拷贝是指在进行对象拷贝时,不仅复制对象中的成员变量的值,还要复制指向动态分配内存的指针所指向的实际数据。这样,原始对象和拷贝对象将拥有各自独立的内存资源,彼此之间不会相互影响;

规定对象只能在栈上创建

// 可以将类的析构函数声明为私有,并提供一个公共的成员函数来释放资源(如果有需要)
// 同时,禁用类的拷贝构造函数和拷贝赋值运算符,防止对象被意外复制
// 这样,就无法在堆上使用 new 来创建该对象,只能在栈上创建

class StackOnlyObject {
public:
    StackOnlyObject() {}
    ~StackOnlyObject() {}
    // 禁用拷贝构造函数和拷贝赋值运算符
    StackOnlyObject(const StackOnlyObject&) = delete;
    StackOnlyObject& operator=(const StackOnlyObject&) = delete;

private:
    // 禁用 new 运算符
    void* operator new(size_t size) = delete;
    void* operator new[](size_t size) = delete;
    void operator delete(void* ptr) = delete;
    void operator delete[](void* ptr) = delete;
};

int main() {
    StackOnlyObject obj; // 可以在栈上创建
    // StackOnlyObject* ptr = new StackOnlyObject(); // 编译错误,无法在堆上创建
    return 0;
}

规定对象只能在堆上创建

// 将类的构造函数声明为私有,同时提供一个公共的静态成员函数来创建对象,并返回指向对象的指针
// 这样,只能通过这个静态成员函数在堆上创建对象

class HeapOnlyObject {
private:
    HeapOnlyObject() {}
    // 禁用拷贝构造函数和拷贝赋值运算符
    HeapOnlyObject(const HeapOnlyObject&) = delete;
    HeapOnlyObject& operator=(const HeapOnlyObject&) = delete;

public:
    static HeapOnlyObject* createObject() {
        return new HeapOnlyObject();
    }
    void destroy() {
        delete this;
    }
};

int main() {
    // HeapOnlyObject obj; // 编译错误,无法在栈上创建
    HeapOnlyObject* ptr = HeapOnlyObject::createObject();
    // 使用对象
    ptr->destroy();
    return 0;
}

引用与指针有什么区别

1)引⽤必须被初始化,指针不必;
2)引⽤初始化以后不能被改变,指针可以改变所指的对象;
3)不存在指向空值的引⽤,但是存在指向空值的指针;

不允许重载的5个运算符

1).* 成员指针访问运算符号
2):: 域运算符
3)Sizeof 长度运算符号
4)?: 条件运算符号
5). 成员访问符

如何定义和实现一个类的成员函数为回调函数

所谓的回调函数,就是预先在系统中对函数进⾏注册,让系统知道这个函数的存在,以后,当某个事件发⽣时,再调⽤这个函数对事件进⾏响应。定义⼀个类的成员函数时在该函数前加 CALLBACK 即将其定义为回调函数,函数的实现和普通成员函数没有区别
参考:C++如何使用类的成员函数作为回调函数

main 函数执行前,还会执行什么代码

全局对象的构造函数会在 main 函数之前执⾏

private,protect,public 三种访问限制类型的区别

private 是私有类型,只有本类中的成员函数和友元函数访问;
protect 是保护型的,本类和继承类可以访问;
public 是公有类型,任何类都可以访问;

static 关键字

一、面向过程设计中的static
1)静态全局变量(全局分配内存、自动初始化为0、外部不可见)
2)静态局部变量(全局分配内存、执行到时初始化,无显示初始化则自动初始化为0、始终存在于静态存储区直到程序结束,但作用域为局部作用域)
3)静态函数(只能在声明它的文件中可见,不能被其它文件使用,其它文件中可以定义相同名字的函数,不会发生冲突)
二、面向对象的static关键字(类中的static关键字)
1)静态数据成员 (静态数据成员只分配一次内存,供所有对象共用、初始化需要加上类名)
2)静态成员函数 (由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长)
C++中static变量的初始化

const 修饰符

const 修饰变量示例说明
const 变量const int a;不能修改值,必须初始化
const 类对象const MyClass a;不能修改成员变量的值,不能调用非 const 函数
指向 const 变量的指针const int * a;指向可变,指向内容不可变
const 指针int * const a;指向不可变,指向内容可变
指向 const 变量的 const 指针const int * const a;指向内容不可变,指向也不可变
const 变量作为函数参数void myfun(const int a);函数内部不能改变此参数
const 返回值const string& myfun(void);用于返回const引用,上层不能使用返回的引用,修改对象
const 成员变量const int a; static const int a;必须在初始化列表初始化,之后不能改变。 static const 成员变量需要单独定义和初始化
const 成员函数void myfun(void) const;this指针为指向const对象的const指针,不能修改非 mutable 的成员变量

定义一个宏,求最小值

#define MIN(A,B) ((A)<(B) ? (A):(B))

编译的过程

1)预处理阶段(包含头文件、去注释、宏展开)
2)编译(源代码转变成汇编语言代码)
3)汇编(汇编语言代码翻译成目标机器指令,生成目标文件)
4)链接(链接库)

sizeof 和 strlen 的区别

1)sizeof是运算符,获得保证能容纳实现所建立的最大对象的字节大小,编译时计算完成;strlen是函数,返回字符串的长度,在运行时才能计算;
2)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以"\0"结尾的;
3)sizeof还可以用函数做参数,比如: short f(); printf(“%d\n”, sizeof(f())); //输出的结果是sizeof(short),即2;
4)数组做sizeof的参数不退化,传递给strlen就退化为指针了;
5)sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数;

c++类型强转

  1. static_cast(任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_const)
  2. const_cast(不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用)
  3. reinterpret_cast(改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型)
  4. dynamic_cast
    (1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查
    (2)不能用于内置的基本数据类型的强制转换
    (3)dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL
    (4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)
    (5)在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全 向上转换,即为子类指针指向父类指针(一般不会出问题);向下转换,即将父类指针转化子类指针 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败 在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试

int (*ptr[10])(int) 代表什么意思

定义了一个有10个元素的指针数组,且数组元素为有整形参数并返回int型的函数指针

以下程序运行后会有什么结果

#include <iostream> 

void GetMemory(char *p, int num) 
{
    p = (char *)malloc( sizeof(char)*num );
} 

int main() 
{
    char *str = NULL;
    GetMemory(str, 100);
    strcpy(str, "hello"); 
    return 0;
} 

程序崩溃,因为调用完GetMemory后,申请的动态内存地址并不能传递给str,main函数里面的str一直是NULL

以下程序运行后会有什么结果

#include <iostream>
using namespace std; 

char *GetMemory(void) 
{
    char p[] = "hello";    
    return p;
} 

int main() 
{
    char *str = NULL;
    str = GetMemory();
    cout << str; 
    return 0;
} 

很可能出现乱码,因为GetMemory返回的指针指向的是已经被释放的栈空间的地址。此地址上原来的“hello”内容很可能已经被新的内容替换

以下程序的输出结果是什么

#include <iostream>
using namespace std; 

class A {
public:
    virtual void print(void) { cout << "A::print()" << endl; }
}; 

class B: public A {
public:
    virtual void print(void) { cout<<"B::print()"<<endl; }
}; 

class C: public A {
public:
    void print(void) { cout<<"C::print()"<<endl; }
}; 

void print(A a) {a.print();} 

int main() {
    A a, *pa, *pb, *pc;
    B b;
    C c; 
    pa = &a;
    pb = &b;
    pc = &c; 
    a.print(); 	// A::print()
    b.print(); 	// B::print()
    c.print(); 	// C::print() 
    pa->print(); // A::print()
    pb->print(); // B::print()
    pc->print(); // C::print() 
    print(a); 	// A::print()
    print(b); 	// A::print()
    print(c); 	// A::print() 
    return 0;
}

求下面函数的返回值(微软)

int func(x)
{
    int countx = 0;
    while(x)
    {
        countx ++;
        x = x & (x-1);
    }
    return countx;
}

假定 x = 9999。答案:8
思路:将x转化为2进制,看含有的1的个数

试写出程序结果

int a = 4;
int & f(int  x)
{
    a = a + x;
    return  a;
}

int main()
{
    int t = 5;
    cout << f(t) << endl;  	// a = 9
    f(t) = 20;           	// a = 20
    cout << f(t) << endl;  	// t = 5 a = 25
    t = f(t);            	// a = 30 t = 30
    cout << f(t) << endl;  	// t = 60
    return 0;
}

以下程序的输出结果是什么

#include <iostream>
using namespace std;

class A {
public:
    virtual ~A() {};
    virtual int test(int a, int b = 3) {
        return a + b;
    }
};

class B: public A {
public:
    int test(int a, int b = 5) {
        return a * b;
    }
};

int main()
{
    A *a = new B();
    int num = a->test(4);	
    cout << num;	\\ 12
    delete a;
    return 0;
}

触发多态时,调用子类的 test 函数,但是默认参数用的是父类的

c++11/14 新特性

参考:C++11新特性

分别写出 bool、int、float、指针类型的变量 a 与“零”的比较语句

1)bool : if(!a) or if(a)
2)int : if(a == 0)
3)float : const EXPRESSION EXP = 0.000001
   if (a < EXP && a >-EXP) 
4)pointer : if(a != NULL) or if(a == NULL)

写出完整版的 strcpy 函数

如果编写一个标准 strcpy 函数的总分值为10,下面给出几个不同得分的答案:

// 2分
void strcpy( char *strDest, char *strSrc )
{
    while( (*strDest++ = * strSrc++) != '\0' );
}

// 4分
// 将源字符串加const,表明其为输入参数,加2分
void strcpy( char *strDest, const char *strSrc )
{
    while( (*strDest++ = * strSrc++) != '\0' );
}

// 7分
void strcpy(char *strDest, const char *strSrc)
{
    // 对源地址和目的地址加非0断言,加3分
    assert( (strDest != NULL) && (strSrc != NULL) );
    while( (*strDest++ = * strSrc++) != '\0' );
}

// 10分
// 为了实现链式操作,将目的地址返回,加3分! 
char * strcpy( char *strDest, const char *strSrc )
{
    assert( (strDest != NULL) && (strSrc != NULL) );
    char *address = strDest;
    while( (*strDest++ = * strSrc++) != '\0' );
    return address;
}

编写类 String 的构造函数、析构函数和赋值函数

已知类String的原型为:

class String
{
public:
    String(const char *str = NULL); // 普通构造函数
    String(const String &other); // 拷贝构造函数
    ~ String(void); // 析构函数
    String & operator =(const String &other); // 赋值函数
private:
    char *m_data; // 用于保存字符串
};
// 普通构造函数
String::String(const char *str)
{
    if ( str == NULL )
    {
	m_data = new char[1];   // 得分点:对空字符串自动申请存放结束标志'\0'的空
	*m_data = '\0';         // 加分点:对 m_data 加 NULL 判断   
    } else {
	int length = strlen(str);
	m_data = new char[length+1];    // 若能加 NULL 判断则更好
	strcpy(m_data, str);
    }
}

// String的析构函数
String::~String(void)
{
    delete [] m_data;
}

// 拷贝构造函数
String::String(const String &other) // 得分点:输入参数为const型
{
    int length = strlen(other.m_data);
    m_data = new char[length+1];    // 加分点:对 m_data 加 NULL 判断
    strcpy(m_data, other.m_data);
}

// 赋值函数
String & String::operator =(const String &other) // 得分点:输入参数为const型
{
    if ( this == &other )  	// 得分点:检查自赋值	
	return *this; 
    delete [] m_data;   	// 得分点:释放原有的内存资源
    int length = strlen( other.m_data );
    m_data = new char[length+1];	// 加分点:对 m_data 加 NULL 判断
    strcpy( m_data, other.m_data );
    return *this;   		// 得分点:返回本对象的引用
}

C++虚函数实现机制

虚函数是实现多态(动态绑定)、接口函数的基础。利用虚表实现
C++对象的内存布局,对象的前8位(64位系统)为虚表指针(vtpr),指向对象所对应的虚表
虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针
同一个类的不同实例共用同一份虚函数表,他们都通过一个虚函数表指针指向该虚函数表
参考:C++中的虚函数表实现机制以及用C语言对其进行的模拟实现
参考:虚函数和虚函数表

C++智能指针

1)c++98中的 std::auto_ptr,现已弃用,存在致命缺陷:拷贝复制时会把原对象所持有的堆内存转移给复制出来的对象,原智能指针变为空指针

2)c++11中的 std::unique_ptr,特意针对 std::auto_ptr 禁止复制,对其持有的堆内存具有唯一拥有权,也就是说引用计数永远是 1,可以通过移动构造 std::move 来转移堆内存

3)c++11中的 std::shared_ptr,共享资源,每多一个资源的引用则计数加1,使用时需要注意:

  • 不应该共享栈对象的 this 给智能指针对象
  • 避免 std::enable_shared_from_this 的循环引用问题

4)c++11中的 std::weak_ptr,不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理的资源的一个访问手段,引入它的目的为协助 std::shared_ptr 工作

参考1:字节跳动C++/Qt PC客户端面试题精选
参考2:Modern C++ 智能指针详解

C++协程

参考:初识协程

互斥锁、可重入锁、读写锁与自旋锁

1)mutex
互斥量mutex是睡眠等待类型的锁,当线程抢互斥锁失败的时候,线程会陷入休眠。优点就是节省CPU资源,缺点就是休眠唤醒会消耗一点时间
依据同一线程是否能多次加锁,把互斥量又分为如下两类:
是:递归互斥量recursive mutex,也称可重入锁,reentrant lock
否:非递归互斥量non-recursive mutex,也称不可重入锁,non-reentrant mutexread-write lock
2)读写锁
又称“共享-独占锁”,对于临界区区分读和写,读共享,写独占
读写锁的特性:
当读写锁被加了写锁时,其他线程对该锁加读锁或者写锁都会阻塞
当读写锁被加了读锁时,其他线程对该锁加写锁会阻塞,加读锁会成功
适用于多读少写的场景
3)spinlock 自旋锁
自旋,更通俗的一个词时“忙等待”(busy waiting)。最通俗的一个理解,其实就是死循环
自旋锁不会引起线程休眠。当共享资源的状态不满足时,自旋锁会不停地循环检测状态(循环检测状态利用了CPU提供的原语Compare&Exchange来保证原子性)。因为不会陷入休眠,而是忙等待的方式也就不需要条件变量。不休眠就不会引起上下文切换,但是会比较浪费CPU

C++类对象的内存分布

参考:C++类对象的内存分布

C++内存配分相关

C++程序运行时进程的内存分布情况
在这里插入图片描述
内存分为5部分,从高地址到低地址为:

  • 栈:空间向下
  • 堆:空间向上
  • 未初始化的数据段(bss):该段数据在程序开始之前由操作系统内核初始化为0,包含所有初始化为0和没有显式初始化的全局变量和静态变量
  • 初始化的数据段(data):初始化的全局变量和静态变量
  • 代码段(text):存放程序的二进制代码

C的储存区分为:

  • 栈:编译器自动分配释放
  • 堆:程序员分配释放
  • 全局区(静态区):全局变量与静态变量存放在一起,初始化与未初始化的全局变量和静态变量分别存放在两块相邻的区域。-程序结束释放
  • 常量区:程序结束释放

C++的储存区分为:

  • 栈:由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变脸、函数参数等
  • 堆:new分配的内存块,他们的释放由程序员负责。若程序员没有释放掉,程序结束后操作系统会自动回收
  • 自由存储区:malloc分配的内存块,他和堆是十分相似的,区别是用free来结束自己的声明
  • 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在C语言中,全局变量和静态变量分为初始化的和未初始化的,在C++中无区分,共同占用同一块内存区
  • 常量存储区:里面存放常量

判断规则:

  • 函数体中定义的变量通常是在栈上
  • 用malloc,new等分配内存的函数分配得到的在堆上
  • 全局变量存在全局区
  • 所有静态变量存在全局区
  • "abcd"字符串常量存放在常量区

C++所占内存数

参考:C++中类型、数组、结构体、类所占字节数

C++设计模式

参考:设计模式

C++排序算法

参考:用C++实现十大经典排序算法

C++内存泄漏检测

参考:C++内存泄露检查的5个方法
参考:C++ 内存泄漏检测方法

C++进程间通信的方式,以及适应的场景

1)管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动,且需要父子进程之间进行通信;
2)命名管道(Named Pipe):也是半双工的通信方式,但它允许无关的进程间通信;
3)信号量(Semaphore):主要作为进程之间的同步机制;
4)消息队列(Message Queue):消息的链接列表,存放在内核中并由消息队列标识符标识;
5)共享内存(Shared Memory):可以被多个进程访问的内存区域,是最快的IPC方式,但需要手动管理内存;
6)套接字(Socket):可用于不同及其间的进程通信,也可用于网络中不同主机间的进程通信;
7)信号(Signal):用于通知接收进程某个事件已经发生;

适用场景:
管道/命名管道:适合父子进程间的通信;
信号量:适合进程间同步;
消息队列:适合不同进程需要互相发送大量数据,但不需要立即响应的场景;
共享内存:适合多个进程需要频繁、即时访问同一块内存区域的场景;
套接字:适合需要网络通信的进程;
信号:适合进程需要通知系统某个事件发生;

如何判断一个程序是 死锁 还是 死循环

通过任务管理器中的软件状态和CPU来区分
参考:C++如何判断一个程序是 死锁 还是 死循环,如何进行问题定位与分析

Qt 元对象系统

Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统
元对象系统由以下三个基础组成:
1)QObject 类,是所有使用元对象系统的类的基类。换句话说只有继承 QObject 才能使用元对象系统;
2)Q_OBJECT 宏,在一个类的 private 部分声明 ,使得类可以使用元对象的特性,如动态属性、信号与槽;
3)MOC(元对象编译器),为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。构建项目时,MOC 工具读取 C++ 源文件,当它发现类的定义里有 Q_OBJECT 宏时,它就会为这个类生成另外一个包含有元对象支持代码的 C++ 源文件,这个生成的源文件连同类的实现文件一起被编译和连接。通常这个新的C++原文件会再以前的C++原文件前面加上moc_作为新的文件名;
可参考:Qt 元对象系统

Qt信号槽的调用流程

1)MOC查找头文件中的signal与slots,标记出信号槽。将信号槽信息储存到类静态变量staticMetaObject中,并按照声明的顺序进行存放,建立索引;
2)connect链接,将信号槽的索引信息放到一个双向链表中,彼此配对;
3)emit被调用,调用信号函数,且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数;
4)active函数在双向链表中找到所有与信号对应的槽索引,根据槽索引找到槽函数,执行槽函数

Qt连接信号槽的方式(connect的第五个参数)

1)Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用 Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用 Qt::QueuedConnection 类型;
2)Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃;
3)Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个;
4)Qt::BlockingQueuedConnection:槽函数的调用时机与 Qt::QueuedConnection 一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个;
5)Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接;

Qt信号槽机制与优势与不足

优点:
1)类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。
2)松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。
3)灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽。

不足:速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍。 原因如下:
1)需要定位接收信号的对象;
2)安全地遍历所有关联槽;
3)编组、解组传递参数;多线程的时候,信号需要排队等待(然而,与创建对象的 new 操作及删除对象的 delete 操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的);

Qt中的常用容器类

1)QVector:基于数组实现的动态数组容器类,支持快速的随机访问和尾部插入操作。适合于需要频繁随机访问的情况;
2)QList:基于双向链表实现的容器类,支持高效的插入和删除操作。适合于需要频繁插入和删除元素的情况;
3)QLinkedList:基于双向链表实现的容器类,支持高效的插入和删除操作,但不支持随机访问。适合于需要频繁插入和删除元素,但不需要随机访问的情况;
4)QSet:基于哈希表实现的集合容器类,支持高效的查找和插入操作,不允许重复元素。适合于需要快速查找元素,且不需要重复元素的情况;
5)QMap:基于红黑树实现的映射容器类,支持高效的查找和插入操作,键值对按照键的大小有序排列。适合于需要按键进行排序和快速查找的情况;
6)QHash:基于哈希表实现的映射容器类,支持高效的查找和插入操作,键值对无序存储。适合于需要快速查找键值对,且不需要按键排序的情况;
7)QStringList:基于QString实现的字符串列表容器类,支持高效的字符串操作,如拼接、查找、替换等。适合于处理字符串列表的情况;
8)QByteArray:基于char数组实现的字节数组容器类,支持高效的二进制数据读写操作。适合于处理二进制数据的情况;

如何进行Qt的性能优化

1)减少内存使用:使用智能指针、减少不必要的拷贝、避免频繁的 new 和 delete 操作等。如使用 QVector 代替 QList,在需要大量存储数据时能够提高性能。
2)减少绘制次数:使用 QPainter 的缓存绘制功能,对于需要频繁绘制的控件,将绘制结果缓存起来,只在需要更新时才进行重绘。
3)使用多线程:在需要大量计算的场景中,将计算放到后台线程中,避免阻塞UI线程,提高响应速度。
4)避免频繁的信号和槽连接:频繁的信号和槽连接会带来额外的开销,可以将一些信号槽的连接放到初始化阶段,避免重复连接。
5)合理使用QML:对于需要频繁更新的UI组件,使用QML实现,能够减少UI线程的工作量,提高UI性能

Qt样式表

参考:Qt 样式表之QSS

QGraphicsView 视图框架

参考:Qt之QGraphicsView入门篇

Qt 中的内存泄漏

参考:Qt 中的内存泄漏

Qt 中如何使用线程

参考:Qt 中开启线程的五种方式

QML 的渲染机制是怎么样的

参考:Qt Quick 渲染之 Scene Graph 详解

如何在QML中注册C++类

参考:Qt 中如何注册一个 C++ 类到 QML

Linux中的进程

  1. Linux中进程的特征
    1)动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的;
    2)并发性:任何进程都可以同其他进程一起并发执行;
    3)独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
    4)异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进;

  2. Linux中进程的组成
    1)进程控制块:使一个在多道程序环境下不能独立运行的程序,成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程;
    2)程序段:是进程中能被进程调度程序在CPU上执行的程序代码段;
    3)数据段:一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行后产生的中间或最终数据;

团队

1)为什么需要招聘这个岗位,上一位员工因为什么原因离职
2)团队规模如何
3)内部有培训吗,培训是否收费
4)晋升空间如何,怎么样进行考核

工作情况

1)薪资结构,底薪、补贴、绩效、年终奖
2)五险一金按照什么基数缴纳
3)试用期多久,是否打折,是否缴纳五险一金,试用期是否有绩效年终奖
4)考勤怎么记录,人脸、指纹、钉钉,是否支持弹性工作
5)加班程度如何,加班如何处理,是否有加班费
6)是否存在出差的情况,出差是否有补贴
7)每年什么时候调薪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值