实习秋招C++知识点总结

本文是针对实习和秋招准备的C++知识点总结,涵盖了C++基础、C++11新特性、类、STL和GDB调试等内容。重点讨论了静态与const的作用、指针与引用的区别、内存管理、智能指针、虚函数以及GDB调试技巧。建议读者根据需求选择性阅读。
摘要由CSDN通过智能技术生成

实习/秋招时按自己需求总结的知识点,内容并不十分详细,建议选择性阅读。

C++基础

如何在main函数之前调用函数
  • C语言,采用__attribute__关键字,void f()attribute((constructor))
  • C++,构造函数构造的全局对象
static和const的作用和区别
static
  • 修饰变量,放在静态存储区,调用时返回的是上次改变之后的结果
  • 修饰函数,避免同名问题
  • 修饰成员变量,多个类的对象共有,且可在对象呗构造出来之前使用
  • 修饰成员函数,多个类的对象共有,且只能访问静态成员变量
const
  • 修饰变量,使其不可变
  • 修饰引用,避免拷贝
  • 修饰指针,常量指针,指针不可变;指向常量的指针,常量不可变
  • 修饰成员函数,函数内部不可修改成员变量
指针和引用的区别
  • 指针初始化可为null,引用必须绑定对象
  • sizeof§返回的是指针字节大小,sizeof(引用)返回的是绑定对象的字节大小
  • 指针有自己的空间,而引用只是别名
野指针
  • 指向非法内存的指针
  • 指向一个被删除对象的指针
new和malloc的区别
  • new是按类型分配的,而malloc是按字节数的大小分配的;
  • new返回的是特定的类型,而malloc返回的是void*,因此使用时需转换,如下:
char *p = new int[12];
char *p = (char*)malloc(sizeof(char)*12);
  • new的对象用delete删除,malloc的对象用free释放
  • new不仅会分配内存,还会执行构造函数
  • new是一个操作符,可以重载,而malloc是一个库函数
  • new会调用构造函数
malloc

malloc用于动态分配内存,为了避免内存碎片以及降低系统调用的开销,malloc采用内存池的方式,先申请大块内存作为堆区,然后将堆区分成若干块,以块作为内存分配的基本单位
malloc在申请内存时,若申请的大小小于128k,则采用brk系统调用,大于128k时,采用mmap系统调用

volatile关键字

百度释义:

易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的; 易恶化的; 易挥发的; 易发散的;

指那些会被编译器之外的,如操作系统,硬件或其他线程修改的变量,被修饰为volatile的变量不会被编译器优化,所谓的优化是指:

编译器读取一个变量时,会定位到这个变量的地址里读,下次再读时,就直接取出这个值,而不用再访问地址

但是被volatile修饰的变量,编译器每次读取都会从变量所在的地址里读(这是因为之前读过的变量可能会被操作系统/硬件/其他线程篡改)

extern关键字

https://blog.csdn.net/weixin_37569048/article/details/83378642
修饰变量,在a.c中定义,如int i=2;在b.c中使用,需

extern int i;

extern “C”

目的是为了C++按照C的方式编译,从而实现C和C++的混合编程
比如用C语言开发了一个DLL库并导出,为了能够让C++也能调用,需要在C++中使用 extern “C” 来强制编译器不要修改函数名
在C++中:

// cpp_test.h
# ifndef CPP_TEST_H_
#define CPP_TEST_H_

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif
#endif
  • C++的运算符重载
    可以理解为编译时多态,如对于函数
    void f(int i, int i)
    编译器会将其编译为
    f_int_int
    即函数名+参数类型的组合,从而实现运算符重载,而C语言是不会做这个修改的,因此按C++编译的函数,无法在源C文件中找到对应的目标文件,从而报错
final和override关键字

C++11中,在类后面加final关键字使其不可被继承:

class A final { // 不可被继承

};

使用override关键字显示指明重载了某一函数

实现一个不能被继承的类
// 初始版,不能用于其他用途
class FinalClassBad {
    private:    // 私有构造,无法被访问,因此不能被继承
        FinalClassBad() {}
        ~FinalClassBad(){}
};
// 进阶版,采用静态方法,用的是单例模式,无法在堆上构造
class FinalClassStatic {
public:
    static FinalClassStatic* Construct(int n) {
        FinalClassStatic * fcs = new FinalClassStatic();
        fcs->n = n;
        return fcs;
    }
    static FinalClassStatic* Deconstruct(FinalClassStatic * a) {
        delete a;
        a = nullptr;
    }
    int n;
private:
    FinalClassStatic(){}
    ~FinalClassStatic(){}
};

FinalClassStatic *fcs = FinalClassStatic::Construct(4);
FinalClassStatic::Deconstruct(fcs);
// 最终版
template< typename T>
class Base {
friend T;   // 友元,可访问私有函数
private:
    T() {}
    ~T(){}
};

// 该类不能被继承
class FinalClass : virtual Base<FinalClass> {   
    public:
        FinalClass(){}
        ~FinalCalss(){}
};

int main()
{
    FinalClass *fc = new FinalClass();
    FinalCalss fc2;
}
#define

宏展开发生在预编译期
有坑,如:

#define MIN(A,B) ((A) <= (B) ? (A) : (B))
调用
least = MIN(*p++, b);
p会被自增两次

inline函数
  • 优点
    直接展开,提高调用速度
  • 缺点
    每个地方都会展开,造成exe体积增大

inline声明的函数编译器可能会无视(如带递归的函数),而未被声明为inline的函数编译器可能优化为inline

C++32位和64位大小
3264
char11
short22
int44
float44
double44
指针48
long48
double88
静态和动态链接库

https://blog.csdn.net/u010935076/article/details/51374388

linuxwindows
静态.a.lib
动态.so.dll
区别
  • 静态链接库使用时会被链接到目标代码中,编译出的程序会更大,而动态链接库是在运行时才会载入相应程序,编译出的程序更小
  • 静态链接库更新时,整个程序都要重新编译,动态链接库更新时只需重新编译动态链接库即可
  • 链接后,删除静态库不影响程序,删除动态库会报错(因为静态库会链接到目标代码中而动态库不会)
创建
  • windows下引入静态库使用
#pragma comment(lib, "test.lib")
  • windows下动态库
// 创建(会生成lib和dll)
extern "C" int __declspec(dllexport) func();
// 使用
#pragma comment(lib, "test.lib")
extern "C" int __declspec(dllimport) func();
  • linux下创建静态库使用ar命令
gcc -c hello.c  # 生成.o文件
ar cr libhello.a hello.o    # 生成.a文件

gcc -o hello main.c -L. -lhello # 链接静态库
  • linux下创建动态库
gcc -c hello.c
gcc -shared -fPIC -o libhello.so hello.o

gcc -o hello main.c -L. lhello
export LD_LIBRARY_PATH=$(pwd)   # 指定动态链接库搜索目录
  • 编译包含动态库的文件
g++ test.cpp -I./kms_api_1.3.1/1.3.1/include -L./kms_api_1.3.1/1.3.1/lib/lib/Tlinux1.2 -lkmshttpapi -L./kms_api_1.3.1/1.3.1/lib/lib/api_deps -lqos_client -o test

每一个头文件都要包含,每一个动态库目录都要添加
export LD_LIBRARD_PATH也要包含上述目录:
export LD_LIBRARY_PATH=$(pwd)/kms_api_1.3.1/1.3.1/lib/lib/Tlinux1.2:$(pwd)/kms_api_1.3.1/1.3.1/lib/lib/api_deps
最好写到makefile中

g++ demo_kmshttp.cpp -I../include -L../lib -lkmshttpapi -L../lib/kms_deps -lqos_client -lcurl -L../lib/kms_deps/ssl_dep -Wl,-rpath=../lib/kms_deps/ssl_dep -lssl -o demo_kmshttp 
  • -shared表示指定生成动态链接库,没有该选项时生成的是可执行文件,无法被别的程序链接
  • -fPIC指示编译器编译成位置无关的代码,从而保证能链接到不同的程序当中
    静态和动态链接库同名时,优先使用动态链接库
程序内存分布

如何避免和判断内存泄漏

C++内存管理包括栈、堆、代码段、数据段(bbs)等,我们说的内存泄漏只出现在

在这里插入图片描述

  • 栈上的内存大小有限,由系统自动管理,申请内存时,就是指针下移,而当超出生命周期时,指针上移,相当于自动回收内存,栈上不会内存泄漏,只会栈溢出
  • 栈通常大小为2M,堆的最大大小和内存有关,通常为3G


VS中栈的默认大小是1M,linux中的默认大小是8M
linux中可以通过ulimit -a命令查看配置

[root@w3cschool.cc ~]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
pending signals (-i) 1024
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 4096virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

避免内存泄漏
  • 使用智能指针
  • new完之后delete,malloc后free,free之后要置指针为null,避免出现野指针
  • 使用RAII技术
  • 尽量使用栈而不是堆上的资源
判断内存泄漏工具

gperftools,Valgrind

RAII

Resources Acquisition Is Initialized
栈上的内存由系统自动管理,当对象超出生命周期时会自动释放,利用这一特性,可以在栈上分配一个对象,每个对象的构造函数在堆上动态分配内存,在析构函数中释放内存,这样,当对象离开生存周期时,对象会自动调用析构函数,拥有的资源就会被释放

RAII的设计思想在于,将由堆上动态分配的内存与在栈上的对象的生存周期相绑定,实现将堆的手动管理交由堆来自动处理

https://www.jianshu.com/p/b7ffe79498be

RAII用于多线程环境中

定义一个锁结构,在构造函数中,持有锁,在析构函数中释放锁,从而避免使用锁时,忘记手动释放锁而引起死锁的问题

CRITICAL_SECTION cs;
class MyLock {
public:
    MyLock() {
        EnterCriticalSection(&cs);
    }
    ~MyLock() {
        ExitCriticalSection(&cs);
    }
private:
    MyLock(const MyLock&);
    MyLock& operator=(const MyLock&);
    
};

unsigned int __stdcall ThreadFunc()
{
    MyLock lock;
    DoSomething(lock);
    //... 
    
    // 退出时调用lock析构函数自动释放临界区资源
}
智能指针

shared_ptr,weak_ptr和unique_ptr
shared_ptr每增加一个引用引用计数+1
weak_ptr是弱引用,不会增加引用计数,用于解决shared_ptr的循环引用造成的内存泄漏问题,
unique_ptr只能指向一个对象,不可复制,赋值,但可以通过std::move转移

  • 用了shared_ptr后,就不要再使用new
  • 为避免循环引用,应当在创建对象时使用shared_ptr,在类内持有其他对象时使用weak_ptr
四种类型转换
  • static_cast
    最常用,C++的隐式转换就是用这个实现的
  • dynamic_cast
    用于多态的转换,因此必须有virual函数才行,如派生类指针转基类指针,但基类转派生类安全,但是会返回nullptr
  • reinterpret_cast
    强制转换,将一个类型的指针转换为另一个类型的指针,最常用的是将一个函数指针转换成另一个函数指针
  • const_cast
    去除const或volatile
内存对齐
  • #pragma pack(n)
  • alignas

一般按4的倍数字节寻址
在这里插入图片描述

https://blog.csdn.net/u013724478/article/details/85010951
C++中可以通过alignas变量设置

C++11新特性

  • 智能指针
  • 类型推导 auto关键字
  • delete关键字
  • nullptr
  • 范围for循环
  • using别名
  • 初始化列表
  • 委托构造,使用其他构造函数构造自身
  • lambda表达式
  • std::forward, std::move

A a(int);   // 构造函数
A a2(a);    // 拷贝构造
A a3 = a2;  // 拷贝构造
A a4;
a4 = a2;    // 拷贝赋值
  • C++用初始化列表初始化成员变量时,只与成员变量在类内定义的先后顺序有关,因为初始化列表初始化与变量在内存的次序有关,而这个次序在对类编译期间就确定了
为什么推荐用初始化列表初始化成员变量

见《Effective C++》条款四

  • const、引用类型必须要在初始化列表中实现
  • 在拷贝函数内部初始化时,其实是赋值操作,会先调用默认构造函数,然后再进行赋值,因此会有一定的开销
优雅的拷贝赋值运算符实现
Widget& operator=(const Widget &rhs)
{
    Bitmap *pOrig = pb; // 保存指针原来指向的内存
    pb = rhs.pb;        // 修改指针指向的内存
    delete pOrig;       // 释放原来指向的内存
    return *this;
}
虚函数

https://www.cnblogs.com/malecrab/p/5572730.html
建议仅用于需要多态的类中,因为虚函数需要在内存中存放,首地址指向实际调用的函数指针,会占用一定内存
在这里插入图片描述

1)子类会继承基类的虚函数表;
2)子类替换已经重写的虚函数指针;
3)追加子类自己的虚函数指针
注意

  • 如果继承了多个类,那么就会生成多个虚函数表;
  • 每个虚函数在类中占用4个字节,即一个指针大小
虚析构函数

用于多态基类,保证派生类的部分能被全部释放掉
如果基类不是虚析构函数,那么释放派生类对象时只会释放掉基类的部分

纯虚析构函数

使其变为抽象基类,仅在多态使用

class WOV {
public:
    virtual ~WOV() = 0; 
};
WOV::~WOV() {}
设计一个不能被拷贝的类
// 这是一个空白基类
class UnCopyable {
protected:
   UnCopyable() {}
   ~UnCopyable() {}
private:
  UnCopyable(const UnCopyable &rhs) {}
  UnCopyable& operator=(const UnCopyable &rhs) {}
};

然后让使用的类继承之:

class Widget : public UnCopyable {
    //...
};
空白类
class EmptyClass {};    // 空白类,但实际上会至少占用一个char的空间,用于内存对齐

但是在被继承时会被优化,被称为EMO(Empty Base Optimization)

class Widget : private EmotyClass{  // 继承空白基类,空白基类不占用空间,因此sizeof(Widget) == sizeof(x)
int x;
//...
};
虚继承

为解决多重继承而出现:
D继承自B,C,而B,C都继承自A,那么在构造D的时候,A就会被构造两次,采用虚继承即可避免此问题

虚函数

STL

迭代器失效
序列式容器

如vector,deque,删除迭代器会导致后续迭代器失效,因此需返回迭代器:

auto iter = vec.begin();
while (iter != vec.end()) {
    // vec.erase(iter); 迭代器失效
    iter = vec.erase(iter);
    ++iter;
}
关联式容器

如map, set, multiset, multimap,底层实现是红黑树,删除迭代器仅导致当前迭代器失效,因此直接累加:

auto iter = m.begin();
while (iter != m.end()) {
    m.erase(iter++);
}
链表

STL的内存管理

小于128k的内存,用二级分配器,即内存池
大于128k的内存,用一级分配器分配,即malloc

  • 采用内存池的方式时,当请求的空间较小,每次都直接从内存池中获取满足大小的内存,分配和释放都在内存池上,开销较小;
  • 采用malloc需要到内存中分配,需要系统调用会切换到内核态

二级分配器
STL的二级分配器采用内存池,维护了8k,16k,24k…128k大小的内存块,使用一个空闲链表存储,每个链表的头结点用数组维护,当分配的内存小于128k时,会找到第一个满足大小的内存,释放时,将内存归还给该链表即可

GDB调试

  • backtrace
  • break
  • continue
  • clear
  • watch, rwatch, awatch
  • delete
  • enable, disable
  • next
  • step
  • catch
  • attach, detach
gdb riskctl_query_svr core.risksss  # 调试core,使用bt打印dump时的堆栈
dir /data/home/laurencexu/code/riskctl_query_proj/src/riskctl_query/    # 将改路径加入到gdb的搜索中
gdb -pid=   # 根据进程号调试
break riskctl_query_svr.cpp: 54 # 在指定文件加入断点
info break  # 查看断点信息
continue    # 执行到下一断点
调试守护进程
  1. 调试子进程的命令 set follow-for-mode child
    在调试守护进程(Daemon进程)的初始化函数时,因为真正做事的是后面启动的子进程,所以直接调试必然是不行的;如果等启动后attach到进程上,那就只有在循环里设断点了,又会错过初始化的过程。不知道这个命令前,做法都是初始化函数里打日志调试,那样就比较繁琐了。这时我们就可以通过设置 follow-for-mode 选项来决定程序启动后只跟踪子进程。
    默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试。只需要设置follow-fork-mode(默认值:parent)和detach-on-fork**(默认值:on)**即可。
    follow-fork-mode 和 detach-on-fork 说明
    parent on 只调试主进程(GDB默认)
    child on 只调试子进程
    parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置
    child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置
    设置方法:set follow-fork-mode [parent|child]

编程

  • rand()
int index = rand() % (e - b + 1) + e; // 生成[b, e]之间的随机数
  • emplace_back
    相比push_back而言避免了构造,降低开销

https://blog.csdn.net/qq_33850438/article/details/80172275 getopt_long的使用

https://blog.csdn.net/u010935076/article/details/51374388 linux下a和so的区别

https://baike.baidu.com/item/%E5%AD%97%E8%8A%82%E9%A1%BA%E5%BA%8F/10059170#4 网络字节顺序 主机地址 ip地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值