C++学习笔记(与安卓平台无关)

C++语法

const指针
const指针(C++入门经典)
const指针(C++入门经典)

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;

C++的四种cast操作符的区别--类型转换

  • 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
    非常底层的类型转换,将一个指针或引用重新解释为另一种类型;
    它不执行任何转换,只是将二进制表示重新解释为另一种类型;
    应该非常小心使用,因为它可能导致类型不兼容的操作,甚至不安全

编程规范

头文件的包含以及命名空间的引入尽量写在cpp里

随机数

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
  1. 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
  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来初始化的。

  1. 当结构体或类的本身或其基类中存在虚函数时,也需要谨慎使用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几种方式:

  1. 二进制Mask,规定宽和高分别占用位的数量和位置
  2. 拼接字符串,比较字符串是否相等
  3. 使用pair
    if (std::pair<int, int>(a1, b1) == std::pair<int, int>(a2, b2))
  4. 使用元组(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;
    }
    );
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351