栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c++疑问笔记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c++疑问笔记

读越界:
    实际长度100, 读取长度200, down
写越界:
    buff长度100, 写入长度200, down

指针跨进程读内存能不能?
为了实现虚拟地址空间各进程私有,每个进程都拥有自己的页目录和页表结构,
对不同进程而言,页目录中的页目录项值(PDE),以及页表中的页表项值(PTE)都是不同的,
因此相同的指针(虚拟地址)被不同的进程映射到的物理地址也是不同的。
这也意味着,在不同进程间传递指针是没有意义的。
解决方案:
1.使用共享内存.这样可以通用OS的帮助,得到同一块物理地址在不同进程里的虚地址.
2.用系统的进程操作函数来读写另一个进程的虚地址.

++X 返回引用                 -> &operator++() {a += 1; return *a;}
X++ 返回值(X必须是左值)     -> operator++() {return a+1;}

C++ 程序中的内存分为两个部分:
    栈:在函数内部声明的所有变量都将占用栈内存
        double d = 29494.99;
    堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存(new/delete)
        double* pd = new double;
        *pd = 29494.99;
        delete pd;
        
        char* pvalue  = NULL;
        pvalue  = new char[20];
        delete [] pvalue;

内存泄漏:
堆内存泄漏(Heap leak):运行期分配,malloc,realloc new
系统资源泄露(Resource Leak):系统分配Bitmap,handle,SOCKET等
ps -aux
BEAM 可以检测四类问题: 没有初始化的变量;废弃的空指针;内存泄漏;冗余计算。而且支持的平台比较多。


内存碎片(空闲内存以小而不连续的方式出现在不同的位置):
碎片类型:
    额外开销:
        内存分配程序需要存储一些描述其分配状态的数据。
        这些存储的信息包括任何一个空闲内存块的位置、大小和所有权,
        以及其它内部状态详情。
        存放这些额外信息最好的地方是它管理的内存
    内部碎片:
        由所需大小四舍五入而产生的多余空间
        (请求3字节, 分配了4字节)
    外部碎片*:
        已分配内存块之间出现未被使用的差额
        (3个连续的内存块,然后使中间的1个内存块空闲)
How:
    少用动态内存分配的函数(尽量使用栈空间)
    分配内存和释放的内存尽量在同一个函数中
    尽量一次性申请较大的内存2的指数次幂大小的内存空间,而不要反复申请小内存(内存池)

在分支比较多时,switch-case的效率肯定比if-else的要高许多
switch和if-else相比,由于使用了BinaryTree算法,绝大部分情况下switch会快一点,
除非是if-else的第一个条件就为true
编译器编译switch与编译if...else...不同,对switch-case会生成一个跳表,不管有多少case,都直接跳转,不需要逐个比较查询
处理时要生成跳表,会占用较多的代码空间,
当case常量分布范围较大但实际的有效值又比较少的时候,switch-case的空间利用率会降低
switch-case仅限于处理分支条件结果为常量的情况,
对于较复杂的条件需要多做程序上的处理,在灵活性上不如if-else

虚表1
virtual method table(VMT)
virtual function table(vftable)
virtual call table
dispatch table
vtable
这些都是虚表的意思。虚表是一种利用程序语言实现的dynamic dispatch机制,
或者说runtime method binding机制,也就是我们说的多态。

虚表2(__vfptr是个void*指针)
伪:
    void* __func[] = {
        &BaseClass::vfunc1,
        &BaseClass::vfunc2,
        ...
        &DeriveClass::vfuncx, // 继承类自己对虚函数, 继承类才能访问
    };
    const void** __vptr = &__func[0]
__vfptr内存位置位于所有的成员变量之前
继承同名vfunc会覆盖基类vfunc中__func位置
只有指针调用, 才会触发动态绑定
https://blog.csdn.net/li1914309758/article/details/79916414

函数只要有virtual,我们就需要把它添加进vTable
每个类(而不是类实例)都有自己的虚表
不同实例共用同一份虚函数表(编译时创建)
虚表存放的位置一般存放在模块的常量段中,从始至终都只有一份

虚表坑
so进程中的对象,如果存在强制类型转换去调用派生类的方法,
由于派生类对象并不实际存在,
如果基类中方法没实例,则会引起调用空方法崩溃
实质上是虚表指针指向类空对象(没实例化方法)
对象 --(*__vptr)--> 虚表 --> 虚函数

参数坑
void ttt(const vector& p1) {cout << "bbbb2" << endl;}
void ttt(uint32_t p1) {cout << "bbbb1" << endl;}
...
ttt({12}); // 这时调的结果是 bbbb1
vector p = {12};ttt(p); // 得这样
or
ttt((vector){12});
引用就是对象。不是指向对象的指针,也不是对象的拷贝,就是对象
当改变引用的值时,也会改变其指示物的值
一旦引用和对象绑定,它无法再被重新指向其他对象
返回一个引用,意味着该函数调用可以出现在赋值运算符的左边,
例如 a[i] = 7;    // 这行调用了 Array::operator[](int)

尽可能使用引用,不得已时使用指针。当你不需要“重新指向(reseating)”时,
引用一般优先于指针被选用。这通常意味着引用用于类的公有接口时更有用。
引用出现的典型场合是对象的表面,而指针用于对象内部。

句柄术语一般用来指获取另一个对象的方法——一个广义的假指针。
这个术语是(故意的)含糊不清的。


使用C/C++进行编程时,一般会使用头文件以使定义和声明分离,并使得程序以模块方式组织。
将函数声明、类的定义放在头文件中,而将函数实现以及类成员函数的定义放在独立的文件中。
但,
当编译器看到模板定义的时候,它不立即产生代码。
只有在看到用到模板时,如调用了函数模板或调用了类模板的对象的时候,
编译器才产生特定类型的模板实例。
如果该模块内没有模板实例,则运行时无法连接到实例而出现链接错误

模板不是函数或类, 正如字面理解, 它并不是一个可以拿来使用的实在的对象
编译器无法识别std::vector::iterator这个名称是一个成员变量还是一个类型
为了防止出现这样的歧义, 正如编译器提示的, 需要在std::vector::iterator 之前加一个关键字typename来显式地说明它是一个类型而, 非成员变量

List x;     // Local object named x (of class List)
List x();   // Function named x (that returns a List)
类没有默认构造函数,那么试图创建一个对象数组将会导致编译时出错
构造函数用初始化列表Fred::Fred() : x_(whatever) { }来初始化成员对象 x_。
这样做最普通的好处是提高性能。
如,whatever表达式和成员变量 x_ 相同,whatever表达式的结果直接由内部的x_来构造——编译器不会产生对象的两个拷贝。
即使类型不同,使用初始化列表时编译器通常也能够做得比使用赋值更好。
不能在构造函数的初始化列表中初始化静态成员数据(通常定义类的静态数据成员的地方是.cpp文件)
命名的构造函数法(Named Constructor Idiom):
    class Point {
     public:
       static Point rectangular(float x, float y);      // 矩形坐标
       static Point polar(float radius, float angle);   // 极坐标
       // 这些 static 方法称为“命名的构造函数(named constructors)”
       // ...
     private:
       Point(float x, float y);     // 矩形坐标
       float x_, y_;
     };
     Point p1 = Point::rectangular(5.7, 1.2);   // 显然是矩形坐标
     Point p2 = Point::polar(5.7, 1.2);         // 显然是极坐标


static initialization order fiasco
存在于不同的源文件x.cpp 和y.cpp的两个静态对象x 和 y。再假定y对象的构造函数会调用x对象的某些方法。

构造/析构 ctor/dtor

日常防:
·空指针
·vec/数组[i]越界
·热更函数指针不更新
    const Fred* p 表明p指向一个const的Fred对象——Fred对象不能通过p修改。
    Fred* const p 表明p是一个指向Fred对象的const指针——可以通过p修改Fred对象,但不能修改p本身。
    cosnt Fred* const p 表明“p是一个指向const Fred对象的const指针”——不能修改p,也不能通过p修改Fred对象。
    const Fred& x 意思是x是Fred对象的一个别名,但不能通过x来修改Fred对象

    public:    void inspect() const;   // const成员函数 该成员保证不修改*this

    “const int* p”意思是“p保证不会修改*p”,而不是说“*p保证不变”
    用const int*指向一个int,不会使这个int变为const。
    int不会通过const int*被修改,但如果有另外一个int*(注意没有const)
    指向该int(这就是“别名”),那么这个int*指针可以用来修改int


for (auto pair : map) : 此时变量内容是个pair结构(map本质), 拷贝
for (auto& pair : map) : 此时变量内容是个pair结构引用
for (auto iter = map.being(); iter != map.end(); ++iter) 这才是个迭代器
另外, 要看迭代是否有修改对象需求...


赋值重载?
struct S {int k = 1;};
#define GN(btype, name) btype name; name.k=2;name;
... {
    GN(S, pDefine);
    cout << "is" << pDefine.k << endl; // is2
}

文本型序列化建议用iostream的>>和<<, 数值可用空格做分隔符, 空格自动会被>>操作符忽略
对于16进制数一定要始终' x'和' u'之后使用相同的位数
处理字符串数据的另一个方法是在字符串前面添加长度前缀

二进制型序列号用read()和write()
打开输入和输出流时使用std::ios::binary
二进制数据有可能被另外一台电脑读取,
必须小心不同的计算机上的印第安编码问题(小印第安编码与大印第安编码)和sizeof问题
    (协议)


申请buff后以地址强转成对象不会自动调用构造函数
struct St& st = pSend->date[i];
需要手动调用
new(&st) St::init();
new(static_cast(_Ptr)) _T1();

隐含变长(双变长)
uint16_t getLen() { return num * sizeof(St); }
sizeof=int64, 而且sizeof不反映可能占内存位长(sizeof(空struct)==0), 应该是:
uint16_t getLen() {
    St* st;
    new(static_cast(st)) St::init();
    return num * st->getLen();
}

static_cast是一个c++运算符 : static_cast ( expression )
把expression转换为type-id类型(没有运行时类型检查来保证转换的安全性)
编译器隐式执行任何类型转换都可由static_cast显示完成
    基/派之间指针或引用的转换
    基本数据类型之间的转换(int-char, int-enum, ...)
    空指针转换成目标类型的空指针
    任何类型的表达式转换成void类型

using
假如子类私有继承父类,子类无法使用父类的成员(变量,函数等)
但是使用using可以访问
template
class P1 {
public:
    sc _age;
    ....
}
class P1Heg : private {
public:
    usint P1::age; // using后变public
    void xxx {age;}
}

左值是指表达式结束后依然存在的持久化对象
右值是指表达式结束时就不再存在的临时对象
所有的具名变量或者对象都是左值,而右值不具名
右值引用,用以引用一个右值,可以延长右值的生命期:
int&& i = 123;
int&& j = std::move(i); // 把i转为右值
错误: int&& j = i; // 这里i是一个左值,右值引用只能引用右值
// 通过移动构造,b指向a的资源,a不再拥有资源
    RValue a, b;
    RValue a1 = std::move(a);
    // 这时候访问a的行为时未定义的
    // 根据移动构造函数的定义,可能是空指针,空字符串
    // move并没有”移动“什么内容,只是将传入的值转换为右值
std::move使用前提:
    1 定义的类使用了资源并定义了移动构造函数和移动赋值运算符,
    2 该变量即将不再使用

&运算不能简单获得0/1 : 0x8 & 0x8 == 8, not 1
uint32_t i的二进制位长 = (uint32_t)(log(i)/log(2)) + 1;
某二进制位是否为1 = i>>(某二进制位 - 1)&1;


协程栈: 切换上下文缓存用
线程栈: 放数据
协程数据在协程栈(在堆上), 非协程环境在线程栈上
(例:co_routine.co_create_env)
假设栈申请了32M空间, 直接memset了一块32*1024*1024, 卒
debug mode下,栈上还有各种调试信息占空间, 秒卒
引申 ↓
编译优化o0, 基本和直观代码一样
>o0的, 可能和直观代码不一样, 例如函数内所有分支都可能预分配内存
某些分支内有32*1024*1024的, 卒


Lambda表达式
[函数对象参数] (参数) mutable ->返回值 {
    ...
}
[]标识一个Lambda的开始,不能省略。函数对象参数有以下形式:
1、空,没有使用任何函数对象参数。
2、=。按值传递
3、&。引用传递方式
4、this。函数体内可以使用Lambda所在类中的成员变量.。
5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝
    因为默认函数是const。要修改传递进来的a的拷贝可以添加mutable修饰符。

按位与:  100101        按位或:    100101     按位异或:100101     按位与:位!=0可做标记
         010111                010111              010111
        =000101               =110111             =110010(同0异1)
左移: 143<<2 = 60 // 10001111<<2 = 00111100
右移: 1001011111101101>>1 = 0100101111110110
常见的二进制位的变换操作
去掉最后一位  101101->10110    x>>1
在最后加一个0    101101->1011010    x<<1
在最后加一个1     101101->1011011    (x<<1)+1
把最后一位变成1    101100->101101      x | 1
把最后一位变成0     101101->101100    (x |1) - 1
最后一位取反    101101->101100    x ^ 1
把右数第K位变成1    101001->101101,k=3    x  | (1<<(k-1))
把右数第K位变成0    101101->101101,k=3    x & ~(1<<(k-1))
右数第k位取反     101001->101101,k=3      x ^ (1<<(k-1))
取末三位    1101101->101     x &7
取末k位    1101101->1101,k=5      x & (1< 取右数第k位    1101101->1,k=4    x >> (k-1)&1
把末k位变成1     101001->101111,k=4    x|(1< 末k位取反     101001->100110,k=4      x^(1< 把右边连续的1变成0      100101111->100100000     x&(x+1)
把右起第一个0变成1    100101111->100111111     x|(x+1)
把右边连续的0变成1    11011000->11011111    x|(x-1)
取右边连续的1    100101111->1111    (x^(x+1))>>1
去掉右起第一个1的左边     100101000->1000     x&(x^(x-1))

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1001702.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号