CPP_HelloWorld

1
2
3
4
5
6
7
8
#include<print>

int main(int argc, char* argv[])
{
std::println("hello world");
return 0;
}

1
2
3
4
5
6
7
8
#include<iostream>

int main(int argc, char* argv[])
{
std::cout << "hello world" << std::endl;
return 0;
}

命名空间

用来解决include两个库,但是两个库有同名同参同返回值的函数的冲突。只要使用namespace::function来调用就可以避免冲突,相当于给函数加了前缀。

using namespace xxx

1
2
3
4
5
6
7
8
#include<print>

int main(int argc, char* argv[])
{
using namespace std;
println("hello world");
return 0;
}

使用using namespace std,使得该块剩下部分的函数如果找不到就优先指定为std中的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<print>

void println(const char* p)
{
;
}


int main(int argc, char* argv[])
{
using namespace std;
println("hello world");
return 0;
}

该情况下优先调用自己的println

using x::y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<print>

void println(const char* p)
{
;
}


int main(int argc, char* argv[])
{
using std::println;
println("hello world");
return 0;
}

使得之后块的y函数始终加x命名空间,此时println为std

上面两种同时使用时,using效果大于using namespace,两个都是using namespace时,需要转换规则。两个都是using时,using效果只针对到下一个using为止。using namespace不可以同时在全局使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<print>

namespace b
{
void println(const char* p)
{
;
}
}


int main(int argc, char* argv[])
{
using std::println;
println("hello world");
using b::println;
println("hello world");

return 0;
}
1
2
3
4
5
6
7
8
9
10
重载解析(Overload Resolution)决胜法则
即使 b::println 与 std::println 同时可见,编译器会根据下面原则选最优匹配:

普通函数优先:非模板函数优于函数模板

参数匹配度:精确匹配(const char* → const char*)优于模板推导或额外转换

在 C++23 <print> 中,std::println 通常是一个可变参函数模板,需要做模板推导;而 b::println(const char*) 则是完全精确匹配的普通函数。

结果:两次调用都选中了 b::println。

面向过程中和C不一样的部分

基本变量

添加bool类型:true和false

添加auto类型,自动识别变量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<print>

bool isZero(int d)
{
return d == 0;
}

int main(int argc, char* argv[])
{
auto d = 0;
if (isZero(d) == true)
std::println("it is zero!");

return 0;
}

foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::vector<int> v{1,2,3,4,5};
for (const auto& x : v) {
std::cout << x << ' ';
}


std::map<std::string,int> m {
{"apple", 3},
{"banana", 5},
{"cherry", 2}
};
for (const auto& [key, value] : m) {
std::cout << key << " -> " << value << "\n";
}

int a[2][3] = {{1,2,3},{4,5,6}};
for (auto& row : a) {
for (auto x : row) {
std::cout << x << ' ';
}
std::cout << '\n';
}

用于快速遍历容器

基本内存分配

new和delete关键字用于分配动态内存

1
2
3
4
5
6
7
int* a = new int(3);
int* b = new char(4);
int* c = new int[10];

delete a;
delete b;
delete[] c;

oprator new和oprator delete是函数,用于进行重载

额外指针类型与类型转换

空指针类型为std::nullptr_t。可以和任意类型非函数/类指针隐式转换。
于此对应的,void*不再自动隐式转换
同时定义了std::ptrdiff_t/std::intptr_t / std::uintptr_t来存放指针和差值,可以直接和值类型转换

1
2
3
int* a = new int(3);
int* b = new int(4);
std::ptrdiff_t diff = a - b;

const,auto,ptr,refer

最痛苦的一集。

const表示变量不可修改。
auto表示自动推断声明的变量类型。
*表示声明指针

引用

&表示声明引用。引用是变量的别名,比如:

1
2
3
4
int a = 1;
int&b = a;//左值引用
b++;
cout<<a;//输出2

左值引用是只能指向一个变量的指针。即: int *const b = a;

eg:

1
2
3
4
5
6
7
8
9
10
11
12
void foo(int& a,int& b){
a = a+b;
b = a-b;
a = a-b;
}

int main(void){
int a=0;
int b=1;
foo(a,b);//交换a,b的值
foo(1,2);//报错,1,2不是左值
}

常量引用是带const的左值引用,不能用它来变更值。

eg:

1
2
3
4
5
6
7
8
9
void foo(const string& a){
cout<<a;
}

int main(void){
string a("hello");
foo(a);//输出hello
foo("world");//输出world
}

右值引用只能绑定一个右值:int&& x = 1;。也许是为了让函数可以传引用,又可以直接传值

eg:移动

1
2
std::string make_str() { return "temp"; }
std::string s = std::move(make_str());

顶层底层const

1
const char*const p = a;

const char为底层const
*const为顶层const

auto

1
2
3
int a =1;
int&b =a;
auto c = b;//c is int

忽略引用,需要时,显式声明

1
2
3
4
5
6
7
int a = 0;
const int b = a,&c=b;
auto k = b;//k is int
auto l = c;//l is int
auto m = &a;//m is int*
auto n = &b;//n is const int*
auto o = &c;//o is const int*

忽略顶层const,需要时,显式声明

1
2
const int a = 0;
auto& b = a;//b is const int&

如果auto有&,保持顶层const

如果原值为数组,推导指针,如果原值为数组&推导数组。

后置类型推导也会用auto推导函数返回值

1
2
3
4
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}

类型转换

C风格:(char)a
CPP风格:char(a)

上面为隐式转换,会依次尝试下面几种显式找到合适的转换方式:

运算符 指针 对象
static_cast<T>(expr) 支持的隐式转换 void*到T*的转换,首先得是可相互转换的(包括指向类的指针和指向该类第一个非静态成员的指针) 用户定义转(父类到子类等)
const_cast<T>(expr) 移除表达式的const或增加表达式的volatile修饰,不能修改底层const 同左,函数指针不受影响 同左,成员函数指针不受影响
dynamic_cast<T>(expr) 沿继承结构向上/下/横向将指针/引用转换为类,可能需要运行时检查
reinterpret_cast<T>(expr) 自由转换,没有限制 自由转换,没有限制 自由转换,没有限制

异常处理

1
2
3
4
5
6
7
8
9
10

try{
throw expr;
}catch(T e1){

}catch(T e2){

}catch(...){

}

面向对象部分

类声明

1
2
3
4
5
6
class myclass{
int a; //private by default
float b;
public:
void change(void);
};
1
2
3
4
5
6
7
8
9
myclass::myclass(){int k = 0,float e = 0.0}{
a = k;
b = e;
}//定义构造函数
myclass tt = myclass(1,1.0);//调用方法1
myclass tt(1,1.0);//调用方法2
myclass tt;//调用方法3

myclass::~myclass(){}//定义析构函数

如果构造函数支持一个参数,支持直接赋值:

classname object = value;

this指针

如果方法涉及到两个或以上的对象,则需要使用this指针。this指针指向调用这个方法的对象的地址

内部的缩写

a等价于this->a

要引用整个表达对象(对象的值)时,用*this,

多态

继承

1
2
3
4
5
6
7
8
9
10
class Person {
public:
std::string name;
int age;
};

class Student : public Person {
public:
std::string studentID;
};

public表示共有还是共有,保护还是保护
protected表示共有变为保护,保护还是保护
private 共有/保护变为私有

多继承

1
2
3
4
5
6
7
class Printer { /* ... */ };
class Scanner { /* ... */ };

class MultiFunctionDevice
: public Printer,
public Scanner
{ /* ... */ };

虚继承

1
2
3
4
class A { /* ... */ };
class B : virtual public A { /* ... */ };
class C : virtual public A { /* ... */ };
class D : public B, public C { /* ... */ };

虚函数

virtual声明关键字

1
2
3
4
5
class Base {
public:
virtual void foo1(int);
virtual void foo2(int);
};
1
2
3
4
5
6
7
8
class Derived : public Base {
public:
// override:编译期检查确实在重写基类的虚函数
void foo1(int) override;

// final:声明后不允许再被进一步重写
void foo2(int) const final;
};

如果含=0表示纯虚函数,必须在某个派生类提供定义,含有纯虚函数的类不能实例化,派生函数同理。

1
2
3
4
5
class Abstract {
public:
virtual void func() = 0; // 纯虚函数
virtual ~Abstract() = default;
};

运算符重载

运算符重载是一种多态。C++根据操作数的数目和类型来决定采用哪种操作。

格式:

1
2
3
4
5
6
7
8
9
10
11
12
myclass operator+(const myclass& t) const;//在类中定义,类内部重载第一个操作数一定是类对象

//实现
myclass myclass::operator+(const myclass & t) const{
myclass k;
...
return k;
}

//使用
a = b.operator+(c);
a = b + c;

重载的限制

  • 重载后的运算符必须至少有一个操作数是用户定义的类型。
  • 使用运算符时不能违反运算符原来的语法规则。(操作数相等,不能修改操作优先级)
  • 不能创建新的运算符
  • 不能重载
    • sizeof:sizeof运算符
    • .:成员运算符
    • .*:成员指针运算符
    • :::作用域解析运算符
    • ?::条件运算符
    • typeeid:一个RTTI运算符
    • const_cast:强制类型转换运算符
    • dynamic_cast:强制类型转换运算符
    • reterpret_cast:强制类型转换运算符
    • stati_cast:强制类型转换运算符
  • 只能通过成员函数重载的运算符
    • =:赋值运算符
    • ():函数调用运算符
    • []:下标运算符
    • ->:通过指针访问类成员的运算符

lambda表达式和函数式编程

在面向对象中用表达式声明和定义一个函数,使其可以捕获上层函数的变量,实现闭包。或是定义只能在某个函数中使用的匿名函数

语法

1
[capture list] (parameter list) [mutable/exception/attribute] -> return type { function body }
  • capture list:捕获列表
    • 值捕获,定义时直接传值
    • 引用捕获,内部使用的是对应变量的引用
    • 隐式捕获,捕获所有外部变量,=表示值捕获,&表示引用捕获
    • 初始化捕获,捕获列表中可以定义变量,且可以自动auto
  • parameter list:传入参数列表,同普通的函数
  • [mutable/exception/attribute]:不加mutable会自动给按值捕获的变量加const
  • type:返回类型,省略后自动推导
  • function body:函数体

举例

自定义排序算法

1
sort(arr,[](int a,int b){return a>b;});

auto泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(void){
auto f = [] (auto x) -> auto
{
if (is_integral<decltype(x)>::value)
{
return x * 2;
}
else if (is_floating_point<decltype(x)>::value)
{
return x / 2;
}
else
{
return x;
}
};
}

捕获类中的成员

1
2
3
4
5
6
class A{
int num;
void test(){
auto func = [*this](){return 2*num;};
}
}

智能指针

自动管理内存的指针,不需要手动释放

1
2
3
4
5
6
7
8
#include <memory>

void demo() {
std::unique_ptr<int> p1 = std::make_unique<int>(42);
*p1 = 100;
auto p2 = std::move(p1);
}

unique指针不能复制,只能移动

1
2
3
4
5
6
7
8
9
10
11
12
#include <memory>
#include <iostream>

void demo() {
auto p1 = std::make_shared<int>(55);
std::cout << p1.use_count();
{
auto p2 = p1;
std::cout << p1.use_count();
}
}

shared指针自动计数并删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <memory>
#include <iostream>

void observe(std::weak_ptr<int> wp) {
if (auto sp = wp.lock()) { // 提升为 shared_ptr
std::cout << *sp;
} else {
std::cout << "expired";
}
}

int main() {
std::weak_ptr<int> wp;
{
auto sp = std::make_shared<int>(10);
wp = sp;
observe(wp); // 输出 10
}
observe(wp); // 输出 expired
}

weak指针,关联一个shared_ptr

移动语义与完美转发

move的函数定义:

1
2
3
4
template <class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg) {
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

遵循折叠规则:

组合形式 折叠后结果
&& &
&&& &
&&& &
&&&& &&

泛型与模板

1
2
3
4
template <class T1>
auto move(T1 x,int y) {
return x+y;
}

自动编译成多个函数,只要使用了不同的类型就有一个

std库

C++ 标准库 | 菜鸟教程