C++语法
const指针
const在函数结尾表示在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数。
const在修饰函数返回值表示返回的是一个const值,承接的变量也应该用const修饰,如
cosnt int a=fun();
template定义
定义函数时参数类型的选择
函数参数可以传值,传引用或者传指针,如何选择依赖于函数的性质
- 对于小对象,优先使用传值参数;
- 对于“没有对象”(用nullptr表示)是有效参数的函数,使用指针参数(记住检测nullptr);
- 指针可以重新指向新对象,而引用不可以
- 任何对于引用形参的处理都会操作到主调函数中的实参(相当于引用地址不变,重新赋值)。而对于指针传递的形参,如果在函数中的改变指针地址,则形参和实参已经脱离,后面对形参的操作将不影响主调函数的实参
- 否则,使用一个引用参数;
摘自:C++程序设计原理与实践(基础篇)P287
把指针变量pointA传入函数b(pointA),执行函数后并不会改变a的指向。如果需要函数b(pointA)改变pointA的指向, 则可以传入指针的指针,即改成b(**pointA)
库文件合并
- *.so文件不能合并,因为其中已经没有重定向信息
- 多个.a文件可以合并成一个动态库/静态库文件
- .a和.so文件不能合并, 因为文件格式不一样
字符串操作
C++中4种方式把字符串和数字连接起来(to_string方式的代码最简洁)
数据结构
Map
- std::map的插入有两种方法:
• insert():
如果insert的key已存在,那么这个insert操作会被忽略。
因此,要覆盖操作需要先调用erase()方法
• 通过下标操作向map中插入元素:
m1[key] = "a";
这样会覆盖原来的值,相当于先erase然后insert。
通过下标访问元素,如果key不存在,会自动在map中插入一个新元素,并将其值设置为默认值(对于整数,值为零;对于有默认构造函数的类型,会调用默认构造函数进行初始化) - std::map提供了两个判断key是否存在的方法。
• map::count(k),返回map中k出现的次数,为0表示不存在。
• map::find(k),如果map中存在按k索引的元素,则返回指向该元素的iterator;如果不存在则返回end()。
• 注意:
查找是否存在某key的元素,不能通过取下标的方式判断,因为这样会使得向map中添加新元素;
std::map::at(key)函数可以直接获取value,但在key不存在时将抛出异常
- std::map的erase方法:
auto n = people.erase ("Jim");// Returns 0 if key not found
auto iter = people.find ("May") ; // Returns end iterator if key not found
if(iter != people.end()){
iter = people.erase (iter) ;// Returns iterator for element after "May"
}
auto iter2 = people.erase(++std:rbegin(people),--std:rend(people)); //Remove all except 1st and last
Vector(类似动态数组)
- std::vector的空间释放:
• clear():
调用vector.clear之后, vector的尺寸(size)将变成zero. 但它的容量(capacity)却并不发生变化
如果你想同时做到清空vector的元素和释放vector的容量, 需要调用swap
std::vector<int>().swap(myVector);
• resize()和reserve():
vector.resize和vector.reserve同样也不释放之前已经占有的空间。从大的size来resize或者reserve到小的size时,如果要释放多余的容量(capacity),同样需要swap:
myVector.reserve(5)
myVector.resize(5)
vector<int>(myVector).swap(myVector);
C++11 新feature
C++ 智能指针详解
c++ unique_ptr、shared_ptr
C++11 的 feature, unique_ptr
std::shared_ptr<T>::operator bool
普通指针转智能指针:
std::shared_ptr<int> mIntSharePtr;
int* intPtr = new int(42);
mIntSharePtr = std::shared_ptr<int> (intPtr);
int* intPtr2 = new int(42);
std::shared_ptr<int> intSharePtr2(intPtr2);
//注意点:
1. intPtr经转换了一次给mIntSharePtr,如果再转换一次给intSharePtr2,相当于同一个普通指针生成了2个共享指针,其中一个销毁时会删除普通指针对象,另一个 也要删除时就出错。所以intSharePtr2用的是intPtr2,而不能用intPtr
智能指针重新赋值时注意:
objectptr = nullptr;// 这行代码会先释放objectptr已经占用的内存,然后再分配内存生成new Object(8)。不调用则顺序相反。这个差异有时候能让某些bug出现或消失。这样的bug一般是Object的构造函数和析构操作的一些操作发生冲突了。
objectptr = std::make_shared<Object>(8);
智能指针的比较运算符会比较原始指针,同类型的共享指针才能使用比较运算符:
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<Test> ptr = std::make_shared<Test>();
std::shared_ptr<Test> ptr2(ptr);
if (ptr == ptr2)
std::cout << "ptr and ptr2 are equal" << std::endl;// 输出此行
return 0;
}
智能指针可以直接判空:
std::ahred_ptr<Test> ptrTest;
if(!ptrTest)
std::cout<<"ptrTest is empty"<<std::endl;
if(ptrTest == NULL)
std::cout<<"ptrTest is empty"<<std::endl;
if(ptrTest == nullptr)
std::cout<<"ptrTest is empty"<<std::endl;
dynamic_cast
由于dynamic_cast是在运行的时候进行检测和转换的,所以会影响到运行时的性能,但好处时转换指针失败时会返回空指针,避免了运行时错误。如果是转换引用失败,则抛出异常std::bad_cast
动态转换只能用于类层次结构中,不能用于基本数据类型之间的转换static_cast
运行时不会动态监测,可能失败情况也返回成功,导致运行崩溃,所以一定是确认能转换成功,才用static的转换方式
静态转换还可以用于将一种基本数据类型转换为另一种基本数据类型,例如 int 转换为 double
基类和派生类的智能指针转换要使用std::dynamic_pointer_cast和std::static_pointer_cast。由于std::dynamic_pointer_cast和dynamic_cast原理一样,std::static_pointer_cast和static_cast原理一样
const_cast
用于移除指针或引用类型的const或volatile修饰;
它可以用于将常量对象转换为非常量对象,以便进行修改;
但使用const_cast改变常量对象的值可能会导致未定义行为reinterpret_cast
非常底层的类型转换,将一个指针或引用重新解释为另一种类型;
它不执行任何转换,只是将二进制表示重新解释为另一种类型;
应该非常小心使用,因为它可能导致类型不兼容的操作,甚至不安全
编程规范
随机数
C++11 随机数
C++11 内建随机数函数库使用教学:随机数产生器与概率分布
比较:
std::default_random_engine e; //没有设置种子,本函数重复执行,每次生成的random1/random2的随机数都一样.
std::uniform_int_distribution<int> u(0, 100);
int random1 = u(e);
int random2 = u(e);
std::default_random_engine engine(time(nullptr));
//设置了种子,每次种子不一样,所以本函数重复执行,生成的random1/random2的随机数也不一样.
std::uniform_int_distribution<int> u(0, 100);
int random1 = u(e);
int random2 = u(e);
//本函数重复执行,每次生成的random1/random2的随机数不一样
int random1 = rand();
int random2 = rand();
虚函数
c++为什么需要虚函数表?
C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别
子类的虚函数地址不会覆盖父类的虚函数地址。在继承关系中,子类会继承父类的虚函数表,并在其自己的虚函数表中添加新的虚函数。
当子类重写(override)父类的虚函数时,子类会在自己的虚函数表中存储新的虚函数地址,而不是覆盖父类的虚函数地址。这意味着通过基类指针或引用调用虚函数时,会根据实际对象的类型来动态地调用相应的虚函数。
这种方式保持了继承链中每个类的独立性,每个类都有自己的虚函数表,包含其自己的虚函数地址以及继承自父类的虚函数地址。这样,通过基类指针或引用调用虚函数时,会在运行时根据对象的实际类型来查找正确的虚函数地址,实现了多态性的特性。
如果想要实现多态性,需要使用指针或引用来调用虚函数(而不是A a = B()这样的对象),这样编译器会进行动态绑定(dynamic binding),在运行时根据对象的实际类型来确定调用哪个函数
左值与右值
快速了解C/C++的左值和右值
从4行代码看右值引用
What does auto&& tell us?
memset
- memset是以字节为单位,初始化内存块。
当初始化一个字节单位的数组时,可以用memset把每个数组单元初始化成任何你想要的值,比如,
memset(data, 1, sizeof(data)); // right
memset(data, 0, sizeof(data)); // right
而在初始化其他基础类型时,则需要注意,比如,
memset(data, 0, sizeof(data)); // right
memset(data, -1, sizeof(data)); // right
memset(data, 1, sizeof(data)); // wrong, data[x] would be 0x0101 instead of 1
- 当结构体类型中包含指针时,在使用memset初始化时需要小心。
比如如下代码中,
struct Parameters {
int x;
int* p_x;
};
Parameters par;
par.p_x = new int[10];
memset(&par, 0, sizeof(par));
当memset初始化时,并不会初始化p_x指向的int数组单元的值,而会把已经分配过内存的p_x指针本身设置为0,造成内存泄漏。同理,对std::vector等数据类型,显而易见也是不应该使用memset来初始化的。
- 当结构体或类的本身或其基类中存在虚函数时,也需要谨慎使用memset
这个问题就是在开头项目中发现的问题,如下代码中,
class BaseParameters
{
public:
virtual void reset() {}
};
class MyParameters : public BaseParameters
{
public:
int data[3];
int buf[3];
};
MyParameters my_pars;
memset(&my_pars, 0, sizeof(my_pars));
BaseParameters* pars = &my_pars;
//......
MyParameters* my = dynamic_cast<MyParameters*>(pars);
程序运行到dynamic_cast时发生异常。原因其实也很容易发现,我们的目的是为了初始化数据结构MyParameters里的data和buf,正常来说需要初始化的内存空间是sizeof(int) * 3 * 2 = 24字节,但是使用memset直接初始化MyParameters类型的数据结构时,sizeof(my_pars)却是28字节,因为为了实现多态机制,C++对有虚函数的对象会包含一个指向虚函数表(V-Table)的指针,当使用memset时,会把该虚函数表的指针也初始化为0,而dynamic_cast也使用RTTI技术,运行时会使用到V-Table,**可此时由于与V-Table的链接已经被破坏,导致程序发生异常
其他
运行时判断类名:
const char* className = typeid(*this).name();
strstr(className,"Water");// strstr函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串;否则,返回NULL。
字符串中嵌入宏定义
#define HEAD \
123; \
123;
#define _SRC(...) #__VA_ARGS__
#define SRC(...) _SRC(__VA_ARGS__)
std::string str_vex = SRC(HEAD)
"abc;"
"abc;";
常用调试命名:
- ldd
用来查看程式运行所需的共享库,常用来解决程式因缺少某个库文件而不能运行的一些问题。
示例:查看test程序运行所依赖的库:
/opt/app/todeav1/test$ldd test
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00000039a7e00000)
libm.so.6 => /lib64/libm.so.6 (0x0000003996400000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000039a5600000)
libc.so.6 => /lib64/libc.so.6 (0x0000003995800000)
/lib64/ld-linux-x86-64.so.2 (0x0000003995400000)
第一列:程序需要依赖什么库
第二列: 系统提供的与程序需要的库所对应的库
第三列:库加载的开始地址
以宽高信息作为key几种方式:
- 二进制Mask,规定宽和高分别占用位的数量和位置
- 拼接字符串,比较字符串是否相等
- 使用pair
if (std::pair<int, int>(a1, b1) == std::pair<int, int>(a2, b2))
- 使用元组(std::tie 和 std::tuple)
if (std::tie(a1, b1, c1) == std::tie(a2, b2, c2))
C++中宏定义包含宏定义的用法:
#define STRING_WRAPPER(s) #s
#define MYDEFINE1 "#version 300 es \n"
const char *shader_vert_2d_30_res =
MYDEFINE1 \
STRING_WRAPPER(
in vec4 aPosition;
in vec4 aTextureCoord;
out vec2 vTextureCoord;
void main() {
gl_Position = aPosition;
vTextureCoord = aTextureCoord.xy;
}
);