C++

C++集锦

C++百问百答

Posted by Bob on December 19, 2018

版本

C++98(1.0)

C++03(TR1,Technical Report1)

C++11(2.0)

C++14

C++17

C++20

各个版本特性

c++11

c++14

c++17

c++20

c++23

编译器及工具

C++ Compilers Wiki :

1.GNU Compiler Collection

2.Clang

3.Visual Studio

4.MinGW

5.Cygwin

6.Cmake

7.Scons

8.emake

9.fastbuild

资料

https://github.com/jobbole/awesome-cpp-cn

https://learn.microsoft.com/en-us/cpp/cpp/cpp-language-reference?view=msvc-170

const用法

const 修饰成员变量

int money = 18;

int const bank = money;

int *card = &money;

//指针所指数据是常量

const int *wechat = &money;

//指针本身是常量

int * const aiplay = &money;

//指针和指针所指数据都不能修改

const int * const bcoin = &money;

顶层const和底层const

指针本身是个一个对象,它又可以指向另外一个对象,因此指针是不是常量以及指针所指的是不是一个常量就是二个相互独立的问题。我们使用术语 “top-level const” 来表示指针本身是一个 const。当一个指针可以指向一个 const 对象时,我们将那个 const 称为 “low-level const”。 更一般来说,top-level const可以表示任意的对象常量,这对任何数据类型抖适用。low-level const则于指针和引用等复合类型的基础类型部分有关。比较特殊的是指针类型既可以是top-level const也可以是low-level const。

int i = 0;
int* const p1 = &i;//不能改变p1的值,这是top-level const
const int ci = 42;//不能改变ci的值,这是top-level const
const int *p2 = &ci;//允许改p2的值,这是low-level const
const int *const p3 = p2;//靠右的const是top-level const,靠左的const是low-level const
const int &r = ci;//用于声明引用的const都是底层const (我的理解是 这里考虑的引用r的基础类型部分)

当执行对象拷贝时,常量是top-level const还是low-level const有区别。top-level const不受影响,执行拷贝操作并不会改变拷贝对象的值,因此拷入和拷出的对象是否是常量都没什么影响。low-level const有限制,拷入和拷出的对象必须具有相同的底层const资格,或者二个对象的数据类型必须能转换。一般来说,非常量可以转换为常量,反之则不行。

Top level case: You make a copy of the const object, so it doesn’t matter if your copy is const or not, because (assuming const-correct types) you can’t modify the original via the copy.

Low level case: You make a copy of a handle to another object. The original handle does not allow modification of the object it refers to. Allowing to ignore the low level const would mean you can obtain a handle that allows you to modify the referred object, breaking const correctness.

const修饰函数参数

void getMoney(const int value) {

    value = 5;//编译失败

}

const修饰成员函数


class Service {

	int money;

	void getMoney(int value) const {

		money = value;//编译失败

	}
};


const修饰函数返回值

///const data,non-const pointer

const int * getMoney(){

	int *money = new int();

	return money;

}

const int *card = getMoney();

变参的函数


#include "stdafx.h"
#include <stdio.h> 
#include <stdlib.h>
#include<stdarg.h>

void format(const char *p_format,...) {
	va_list argp;
	va_start(argp, p_format);
	const unsigned int BUFFER_SIZE = 16384;
	char buf[BUFFER_SIZE + 1];
	vsnprintf(buf, BUFFER_SIZE, p_format, argp);
	printf("%s",buf);
	va_end(argp);
};


int main()
{
	format("you are %s / %s","boy","girl");
	system("Pause");
	return 0;
}

传参数

传值 by value

//调用这个函数时先额外申请两个内存单元,存形参a和b的值1和2

void swap(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
	int m = 1;
	int n = 2;
	swap(m, n);
	system("Pause");
	return 0;
}

传址

//调用这个函数时先额外申请两个内存单元,存形参a和b的地址

void swap(int *a, int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int m = 1;
	int n = 2;
	swap(&m, &n);
	system("Pause");
	return 0;
}

引用 by reference

//函数时并不重新分配内存空间,形参和实参相同

void swap(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
	int m = 1;
	int n = 2;
	swap(m, n);
	system("Pause");
	return 0;
}


虚函数

虚函数,在父类必须实现该函数; 纯虚函数,在父类中不需要实现该函数,子类必须实现。


#include <iostream>

using namespace std;

class BaseView {
public:
	BaseView() {  }
	virtual void Paint() { cout << "Paint BaseView" << endl; };
	virtual void Print() = 0;
};

class Button:BaseView {
public:
	Button() { Paint(); }
	virtual void Paint() { cout << "Paint Button" << endl; };
	virtual void Print() { cout << "Print Button" << endl; };
};


int main()
{
	Button btn;
	btn.Paint();
	system("Pause");
	return 0;
}

//结果: Paint BaseView  Paint Button Paint Button

explicit构造函数


#include <iostream>

using namespace std;

class BaseView {
public:
	int id = 0;
	explicit BaseView(int _id) { id = _id; }
};

int main()
{
	BaseView bv1(10);
	BaseView bv2 = BaseView(10);
	BaseView bv3 = 10; //编译错误  不允许隐式的转换

	system("Pause");
	return 0;
}

继承


#include <iostream>

using namespace std;

class BaseView {
public:
	BaseView() { cout << "BaseView()" << endl; };
	~BaseView() { cout << "~BaseView()" << endl; };
};

class Button:public BaseView {
public:
	Button() { cout << "Button()" << endl; };
	~Button() { cout << "~Button()" << endl; };
};


int main()
{
	Button* btn = new Button();
	delete btn;
	btn = 0;


	/*

	输出:
	
	BaseView()
	Button()
	~Button()
	~BaseView()

	*/

	system("Pause");
	return 0;
}

重载

成员函数被重载的特征:

1.相同的范围(在同一个类中); 2.函数名字相同; 3.参数不同; 4.virtual 关键字可有可无。


#include <iostream>

using namespace std;

class BaseView {
public:
	BaseView() {  }
	
};

class Button:BaseView {
public:
	Button() {  }
	void init() {};
	void init(int width, int height) {};
	void init(int width, int height,int length) {};
};


int main()
{
	Button btn;
	system("Pause");
	return 0;
}

int func(int a); 和 int func(const int a); 不构成函数重载 对于 int func(int a); 和 int func(const int a);,在参数传递时采用的是值传递方式。在值传递中,const 修饰符不会影响函数调用时参数的匹配。也就是说,当你调用 func 函数并传入一个整数时,编译器无法依据这个整数是否被 const 修饰来判断该调用哪个函数,因为在值传递过程中,函数接收到的是参数的副本,const 修饰符在这个副本上是否存在对调用者来说是不可见的。

覆盖

覆盖是指子类函数覆盖父类函数,特征是:

1.不同的范围(分别位于子类与父类); 2.函数名字相同; 3.参数相同; 4.基类函数必须有virtual 关键字。



#include <iostream>

using namespace std;

class BaseView {
public:
	BaseView() {  }
	virtual void init(int width, int height) { cout << "BaseView.init(int width, int height)" << endl; };
};

class Button:BaseView {
public:
	Button() {  }
	void init(int width, int height) { cout << "Button.init(int width, int height)" << endl; };
	
};


int main()
{
	Button btn;
	btn.init(10,20);
	system("Pause");
	return 0;
}

//结果:Button.init(int width, int height)

隐藏

如果子类的函数与父类的函数同名,并且参数也相同,但是父类函数没有virtual关键字。此时,父类的函数被隐藏(注意别与覆盖混淆)。



#include <iostream>

using namespace std;

class BaseView {
public:
	BaseView() {  }
	void init(int width, int height) { cout << "BaseView.init(int width, int height)" << endl; };
};

class Button:BaseView {
public:
	Button() {  }
	void init(int width, int height) { cout << "Button.init(int width, int height)" << endl; };
	
};


int main()
{
	Button btn;
	btn.init(10,20);
	system("Pause");
	return 0;
}

//结果:Button.init(int width, int height)

隐藏

如果子类的函数与父类的函数同名,但是参数不同。此时,不论有无virtual关键字,父类的函数将被隐藏(注意别与重载混淆)


#include <iostream>

using namespace std;

class BaseView {
public:
	void init(int width, int height) { cout << "BaseView.init(int width, int height)" << endl; };
	virtual void resetSize(int width, int height) { cout << "BaseView.resetSize(int width, int height)" << endl; };
	virtual void resetPos(int x, int y) { cout << "BaseView.resetPos(int x, int y)" << endl; };
};

class Button:public BaseView {
public:
	void init(float width, float height) { cout << "Button.init(float width, float height)" << endl; };
	virtual void resetSize(float width, float height) { cout << "Button.resetSize(float width, float height)" << endl; };
	virtual void resetPos(int x, int y) { cout << "Button.resetPos(int x, int y)" << endl; };
};


int main()
{
	Button btn;
	btn.init(10, 20);
	btn.resetSize(10, 20);
	btn.resetPos(10,20);



	Button btn2;
	BaseView *bptr1 = &btn2;
	Button *bptr2 = &btn2;

	//bad
	bptr1->init(10, 20);
	bptr2->init(10,20);

	//bad
	bptr1->resetSize(10, 20);
	bptr2->resetSize(10, 20);

	//good
	bptr1->resetPos(10, 20);
	bptr2->resetPos(10, 20);


	/*

	结果:
	Button.init(float width, float height)
	Button.resetSize(float width, float height)
	Button.resetPos(int x, int y)

	BaseView.init(int width, int height)
	Button.init(float width, float height)

	BaseView.resetSize(int width, int height)
	Button.resetSize(float width, float height)

	Button.resetPos(int x, int y)
	Button.resetPos(int x, int y)

	*/

	system("Pause");
	return 0;
}

static_cast

static_cast主要用于在相关类型之间进行转换,这些类型通常是有一定关联的,比如基本数据类型之间的转换(如int和float)、类层次结构中的向上转换(将派生类指针或引用转换为基类指针或引用)。它是一种编译时的类型转换操作,编译器会在编译阶段检查转换是否合理。

int i = 5;
float f = static_cast<float>(i);

class Base {
public:
    virtual void func() {}
};
class Derived : public Base {
public:
    void func() override {}
};
int main() {
    Derived d;
    Base* b = static_cast<Base*>(&d);
    return 0;
}

dynamic_cast

dynamic_cast主要用于在类的继承层次结构中进行安全的向下转换(将基类指针或引用转换为派生类指针或引用)或者交叉转换(在有多个继承分支的情况下)。与static_cast不同的是,dynamic_cast会在运行时检查转换是否有效,如果转换无效(比如基类指针实际指向的对象不是目标派生类的对象),对于指针类型的转换,它会返回nullptr;对于引用类型的转换,它会抛出std::bad_cast异常。

int main() {
    Base* b = new Derived;
    Derived* d = dynamic_cast<Derived*>(b);
    if (d) {
        // 转换成功后的操作
        d->func();
    }
    else {
        // 转换失败后的操作
        // 这里可能是b实际指向的不是Derived对象
    }
    delete b;
    return 0;
}

int main() {
    Base& b_ref = *new Derived;
    try {
        Derived& d_ref = dynamic_cast<Derived&>(b_ref);
        d_ref.func();
    }
    catch (std::bad_cast& e) {
        // 处理异常,当转换失败时会进入这里
    }
    return 0;
}

reinterpret_cast

reinterpret_cast是一种比较危险的类型转换操作,它可以将一种类型的指针转换为另一种完全不同类型的指针,或者将一个整数转换为指针,或者将指针转换为整数等。它只是简单地对二进制数据进行重新解释,不考虑类型之间的语义关系。这种转换通常用于底层的、和硬件或系统接口相关的编程场景,如直接操作内存地址等,但很容易导致程序出现错误。

int main() {
    int i = 10;
    // 将int*转换为char*
    char* c = reinterpret_cast<char*>(&i);
    // 这种操作可能会破坏类型的安全性
    // 这里只是简单地将int类型的内存表示按照char类型来解释
    return 0;
}

const_cast

修改const限定符示例 有时候,我们可能会遇到这样的情况,比如有一个函数接收一个const参数,但在函数内部其实需要对这个参数进行一些非const的操作。这可能是因为这个函数被设计得不够合理,或者是因为在某些特定的情况下(比如对一个原本不是const的对象进行了const引用传递,但在函数内部又需要修改它),需要去除const属性来进行操作

void updateCounter(const int* const pCounter) {
    // 这里通过const_cast去掉指针和指针所指对象的const属性
    int* nonConstCounter = const_cast<int*>(pCounter);
    (*nonConstCounter)++;
}
int main() {
    int counter = 0;
    updateCounter(&counter);
    return 0;
}

修改volatile限定符示例 volatile关键字用于告诉编译器,变量的值可能会在程序的控制流之外被改变,比如被硬件或者其他异步线程修改。const_cast也可以用于修改volatile限定符。

volatile const int* pReg = getHardwareRegisterAddress();
int* nonVolatileReg = const_cast<int*>(pReg);
// 进行一些非volatile的操作,例如在特定的初始化阶段设置寄存器的初始值
*nonVolatileReg = 0;

enum class

在 C++ 中,enum class(枚举类)是一种强类型的枚举。它解决了传统枚举(enum)的一些问题,比如枚举值的作用域问题和类型安全性问题。

enum class Color {
    RED,
    GREEN,
    BLUE
};
Color c = Color::RED;
// int i = c; // 这是错误的,不能隐式转换
int i = static_cast<int>(c); // 正确,需要显式转换

enum OldColor {
    RED,
    GREEN,
    BLUE
};
OldColor c = OLD_COLOR::RED;
int i = c; // 可以隐式转换为整数

枚举类中的枚举值的名字是在枚举类的作用域内的,这避免了名字冲突。

enum Fruit {
    APPLE,
    BANANA,
    ORANGE
};
enum Vegetable {
    CARROT,
    BROCCOLI,
    APPLE // 这里会产生命名冲突,因为APPLE已经在Fruit枚举中定义过了
};

enum class AnotherFruit {
    APPLE,
    BANANA,
    ORANGE
};
enum class AnotherVegetable {
    CARROT,
    BROCCOLI,
    APPLE // 不会产生命名冲突,因为作用域不同
};

Switch中使用

Color c = Color::GREEN;
switch (c) {
case Color::RED:
    std::cout << "It's red." << std::endl;
    break;
case Color::GREEN:
    std::cout << "It's green." << std::endl;
    break;
case Color::BLUE:
    std::cout << "It's blue." << std::endl;
    break;
}

类型精准

enum OldEnum {
    VALUE1 = 1,
    VALUE2 = 2,
    VALUE3 = 3
};

enum class NewEnum : unsigned char {
    VALUE1 = 1,
    VALUE2 = 2,
    VALUE3 = 3
};

lvalue和rvalue

An lvalue has an address that your program can access. Examples of lvalue expressions include variable names, including const variables, array elements, function calls that return an lvalue reference, bit-fields, unions, and class members.

A prvalue expression has no address that is accessible by your program. Examples of prvalue expressions include literals, function calls that return a nonreference type, and temporary objects that are created during expression evaluation but accessible only by the compiler.

右值引用必须绑定右值的引用,通过&&来获得右值引用。右值引用必须绑定一个将要销毁的对象。 左值引用,我们不能将其绑定到要求转化的表达式/字面常量/返回右值的表达式。


int &l = 0;//lvaule reference不接受 rvalue,非 const 左值引用 'l' 到 int 类型不能绑定到类型 int 的右值

int &&r = 10;//rvaule reference 接受 rvaule

const char (*p)[12] = &"hello world"; //hello world这个字面值是lvalue,它可以取地址。

“std::move ()” 是 C++ 标准库中的一个函数。它的作用是将一个对象的状态从一个位置 “转移” 到另一个位置,而不是进行传统的拷贝操作。它主要用于提高性能,特别是在处理资源拥有型对象(如智能指针等)时,可以避免不必要的拷贝,直接将资源的所有权转移给另一个对象。例如,将一个临时对象的资源转移给一个正在构造的对象,或者在容器中进行高效的元素移动操作。


#include <algorithm>
#include <iostream>
#include <string>

class student
{
public:
    student(const int id, const std::string& name);

    student(const student &s);

    student(student &&s) noexcept;

    student& operator=(const student& s);

    student& operator=(student&& s) noexcept;

    ~student();

    int id;
    std::string name;
};

//constructor
student::student(const int id, const std::string& name)
{
    this->id = id;
    this->name = name;

    std::cout << "Constructor called for " << name << '\n';
}

//copy constructor
student::student(const student& s)
{
    this->id = s.id;
    this->name = s.name;

    std::cout << "& Constructor called for " << name << '\n';
}

//move constructor
student::student(student&& s) noexcept
{
    this->id = s.id;
    this->name = std::move(s.name);

    std::cout << "&& Constructor called for " << name << '\n';
}

//copy assignment operator
student& student::operator=(const student& s)
{
    if (this != &s)
    {
        this->id = s.id;
        this->name = s.name;
    }
    std::cout << "= Constructor called for " << name << '\n';
    return *this;
}

//move assignment operator
student& student::operator=(student&& s) noexcept
{
    if (this != &s)
    {
        this->id = s.id;
        this->name = std::move(s.name);
    }
    std::cout << "&&= Constructor called for " << name << '\n';
    return *this;
}

//destructor
student::~student()
{
    std::cout << "Destructor called for " << name << '\n';
}


int main(int argc, char* argv[])
{
   

    student s1(1, "John");
    student s2 = std::move(s1);//把s1的资源转移到s2,s1的资源被释放,s2的资源被保留
    /*

    Constructor called for John
    && Constructor called for John
    Destructor called for John //s2
    Destructor called for  //s1

     */
    return 0;
}

array


int arr[10];//含有10个整数的数组

int *parr[20];//含有20个整数指针的数组

int (*Parry)[10] = &arr;//Parray是一个指针,指向一个含有10个整数的数组

int (&Rarray)[10] = arr;//Rarray是一个引用,指向一个含有10个整数的数组

string nums[] = {"one","two"};
string *p  = &nums[0];//p指向nums的第一个元素
string *p2 = nums;//等价于p2 = &nums[0].用到数组名字的地方,编译器都会自动将其替换为一个指向数组首元素的指针

char str[12] = "hello world"; //"hello world" 的内容就存于栈内存里
const char* str2 = "hello world";//"hello world" 会被存储在只读数据段(也称作常量区)

func

bool fun(int a, int b);//该函数的类型是 bool(int,int),函数类型是和函数名无关的
bool (*p)(int a, int b);//p是一个指向函数的指针,这个函数有二个参数,一个返回值。
p = fun//当我们把函数名作为一个值使用时,改函数自动转化为指针.p指向名为fun的函数
p = &fun;//等价于上面一句。
//我们可以直接使用指向函数的指针调用改函数,无须提前解引用指针
bool b1 = pf(1,2);//调用func函数
bool b2 = (*pf)(1,2);//等价于上一句
bool b3 = func(1,3);//另一个等价