赞
踩
当构造函数涉及到动态存储分配空间时,要⾃⼰写拷贝构造函数,并且要深拷贝
1)该类的成员变量是个引用;
2)该类的成员变量是const类型;
3)该类是继承一个基类,并且基类中有构造函数,构造函数里有参数;
4)该类的成员变量类型是类类型,而该类的构造函数带参数时
1).* 成员指针访问运算符号
2):: 域运算符
3)Sizeof 长度运算符号
4)?: 条件运算符号
5). 成员访问符
所谓的回调函数,就是预先在系统中对函数进⾏注册,让系统知道这个函数的存在,以后,当某个事件发⽣时,再调⽤这个函数对事件进⾏响应。定义⼀个类的成员函数时在该函数前加 CALLBACK 即将其定义为回调函数,函数的实现和普通成员函数没有区别
参考:C++如何使用类的成员函数作为回调函数
全局对象的构造函数会在 main 函数之前执⾏
private 是私有类型,只有本类中的成员函数和友元函数访问;
protect 是保护型的,本类和继承类可以访问;
public 是公有类型,任何类都可以访问;
1)引⽤必须被初始化,指针不必;
2)引⽤初始化以后不能被改变,指针可以改变所指的对象;
3)不存在指向空值的引⽤,但是存在指向空值的指针;
一、面向过程设计中的static
1)静态全局变量(全局分配内存、自动初始化为0、外部不可见)
2)静态局部变量(全局分配内存、执行到时初始化,无显示初始化则自动初始化为0、始终存在于静态存储区直到程序结束,但作用域为局部作用域)
3)静态函数(只能在声明它的文件中可见,不能被其它文件使用,其它文件中可以定义相同名字的函数,不会发生冲突)
二、面向对象的static关键字(类中的static关键字)
1)静态数据成员 (静态数据成员只分配一次内存,供所有对象共用、初始化需要加上类名)
2)静态成员函数 (由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长)
C++中static变量的初始化
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)链接(链接库)
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是个操作符不是个函数;
定义了一个有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新特性
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 函数的总分值为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的原型为:
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++对象的内存布局,对象的前8位(64位系统)为虚表指针(vtpr),指向对象所对应的虚表
虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针
同一个类的不同实例共用同一份虚函数表,他们都通过一个虚函数表指针指向该虚函数表
参考:C++中的虚函数表实现机制以及用C语言对其进行的模拟实现
参考:虚函数和虚函数表
参考1:字节跳动C++/Qt PC客户端面试题精选
参考2:Modern 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++程序运行时进程的内存分布情况
内存分为5部分,从高地址到低地址为:
C的储存区分为:
C++的储存区分为:
判断规则:
参考:设计模式
通过任务管理器中的软件状态和CPU来区分
参考:C++如何判断一个程序是 死锁 还是 死循环,如何进行问题定位与分析
参考:C++内存泄露检查的5个方法
参考:C++ 内存泄漏检测方法
Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。
元对象系统由以下三个基础组成:
1)QObject 类,是所有使用元对象系统的类的基类。换句话说只有继承 QObject 才能使用元对象系统;
2)Q_OBJECT 宏,在一个类的 private 部分声明 ,使得类可以使用元对象的特性,如动态属性、信号与槽;
3)MOC(元对象编译器),为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。构建项目时,MOC 工具读取 C++ 源文件,当它发现类的定义里有 Q_OBJECT 宏时,它就会为这个类生成另外一个包含有元对象支持代码的 C++ 源文件,这个生成的源文件连同类的实现文件一起被编译和连接。通常这个新的C++原文件会再以前的C++原文件前面加上moc_作为新的文件名;
可参考:Qt 元对象系统
1)MOC查找头文件中的signal与slots,标记出信号槽。将信号槽信息储存到类静态变量staticMetaObject中,并按照声明的顺序进行存放,建立索引;
2)connect链接,将信号槽的索引信息放到一个双向链表中,彼此配对;
3)emit被调用,调用信号函数,且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数;
4)active函数在双向链表中找到所有与信号对应的槽索引,根据槽索引找到槽函数,执行槽函数
1)Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用 Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用 Qt::QueuedConnection 类型;
2)Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃;
3)Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个;
4)Qt::BlockingQueuedConnection:槽函数的调用时机与 Qt::QueuedConnection 一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个;
5)Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接;
优点:
1)类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。
2)松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。
3)灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽。
不足:速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍。 原因如下:
1)需要定位接收信号的对象;
2)安全地遍历所有关联槽;
3)编组、解组传递参数;多线程的时候,信号需要排队等待(然而,与创建对象的 new 操作及删除对象的 delete 操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的);
1)QVector:基于数组实现的动态数组容器类,支持快速的随机访问和尾部插入操作。适合于需要频繁随机访问的情况;
2)QList:基于双向链表实现的容器类,支持高效的插入和删除操作。适合于需要频繁插入和删除元素的情况;
3)QLinkedList:基于双向链表实现的容器类,支持高效的插入和删除操作,但不支持随机访问。适合于需要频繁插入和删除元素,但不需要随机访问的情况;
4)QSet:基于哈希表实现的集合容器类,支持高效的查找和插入操作,不允许重复元素。适合于需要快速查找元素,且不需要重复元素的情况;
5)QMap:基于红黑树实现的映射容器类,支持高效的查找和插入操作,键值对按照键的大小有序排列。适合于需要按键进行排序和快速查找的情况;
6)QHash:基于哈希表实现的映射容器类,支持高效的查找和插入操作,键值对无序存储。适合于需要快速查找键值对,且不需要按键排序的情况;
7)QStringList:基于QString实现的字符串列表容器类,支持高效的字符串操作,如拼接、查找、替换等。适合于处理字符串列表的情况;
8)QByteArray:基于char数组实现的字节数组容器类,支持高效的二进制数据读写操作。适合于处理二进制数据的情况;
1)减少内存使用:使用智能指针、减少不必要的拷贝、避免频繁的 new 和 delete 操作等。如使用 QVector 代替 QList,在需要大量存储数据时能够提高性能。
2)减少绘制次数:使用 QPainter 的缓存绘制功能,对于需要频繁绘制的控件,将绘制结果缓存起来,只在需要更新时才进行重绘。
3)使用多线程:在需要大量计算的场景中,将计算放到后台线程中,避免阻塞UI线程,提高响应速度。
4)避免频繁的信号和槽连接:频繁的信号和槽连接会带来额外的开销,可以将一些信号槽的连接放到初始化阶段,避免重复连接。
5)合理使用QML:对于需要频繁更新的UI组件,使用QML实现,能够减少UI线程的工作量,提高UI性能
参考:Qt 样式表之QSS
参考:Qt 中的内存泄漏
参考:Qt Quick 渲染之 Scene Graph 详解
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。