c++笔记1(参考learncpp.com)

1、变量初始化

【推荐】brace initialization 大括号初始化

int width{5}; //brace initialization
int b{}; //0,等效int b{0}
int a{4.5}; //会提醒错误,而int a=4.5不会(输出4)

direct initialization

copy initialization

int width( 5 ); //direct initialization
int width = 5; //copy initialization

{}与=区别,{}用来初始化,初始化只有1次。=用来赋值,赋值可以有很多次。{}初始化更安全,因为有类型检查。

2、避免使用using namespace

【推荐】使用 ::

#include <iostream>
//using namespace std; //全局权限太大

int main()
{
    std::cout << "Hello World!\n"; //推荐 ::
    return 0;
}

避免使用using namespace 在项目的顶部,全局权限太大,会对自定义函数造成干扰。

3、头文件

【推荐】

cpp中头文件引入顺序:自定义、第三方、标准库。

② .h中尽量少#include(除非不引入就报错),大多在cpp中引入。

③ .h中使用如下,或者#pragma once(前者都兼容,此未必),防止引入冲突。

#ifndef ADD_H  //头文件名全大写_H
#define ADD_H

int add(int x, int y);
 
#endif

④ 头文件中只声明(变量、函数)而不定义(全局常量除外),定义在cpp中实现。注意自定义头文件使用双引号。避免#include里带路径,应该在IDE里设置。

头文件可以避免,多个cpp相互调用时大量的声明,这些声明放到头文件里即可。

从下图可知,编译阶段识别声明(头文件),链接阶段才识别定义(cpp文件)。

 

4、变量

注意下图红框,不同系统不同位数不同编译器无法保证具体的字节数,只能保证最小字节。

4.1 针对浮点型,cout 默认只输出6个有效数字。

浮点型存在 rounding error(舍入误差),不要太相信浮点型数据,在运算与比较时要留意。

【推荐】用 double 而不是 float。

 从上图得知,4bytes类型(如float)的精度是7个有效数字,8bytes类型(如double)的精度是16个有效数字

#include <iostream>
#include <iomanip> // for std::setprecision()

int main()
{
    float f{ 123456789.0f }; // f has 10 significant digits
    double d{ 123456789.0 };
    std::cout << std::setprecision(9); // 显示9个数字
    std::cout << f << '\n'; //123456792,注意第8、9位的数字异常
    std::cout << d << '\n'; //123456789
    return 0;
}

#include <iostream>
int main()
{
    std::cout << 9.87654321f << '\n';
    std::cout << 987.654321f << '\n';
    std::cout << 987654.321 << '\n';
    std::cout << 9876543.21f << '\n';
    std::cout << 0.0000987654321f << '\n';

    return 0;
}

 4.2 布尔类型

true、false是以整型1、0存储的,本质上是整型。true、false与bool一样,也是关键字。

#include <iostream>

int main()
{
    //std::cout << std::boolalpha; //以true、false输出,而不是1、0
    bool b1{ true };
    bool b2{ false };
    bool b3{}; //默认false
    std::cout << b1 << '\n'; //1
    std::cout << b2 << '\n'; //0
    std::cout << b3 << '\n'; //0

    return 0;
}

5、静态转换 static_cast,char类型

格式:static_cast<new_type>(expression)

注意:不会改变expression

#include <iostream>

int main()
{
    char ch{ 'a' }; //a
    char ch2{ 97 }; //a
    std::cout << static_cast<int>(ch) << '\n'; //转int型,97

    return 0;
}

避免使用wchar_t(面向Windows API),char16_t、char32_t(c++11),char8_t(c++20),除非必须兼容Unicode字符集。

对于字符串,不可以赋值和传递函数参数。

    //char s[]{ "你好" }; //C-style strings
    std::string s{ "你好" }; //c++风格
    std::cout << s << '\n'; 

6、进制、显示

2、8、16进制开头: 0b(c++14)、0、0x

显示,std::hex、std::oct、std::dec之后,会改变显示方式。

    //转10进制
    int a{ 0b1000'0001 }; //2进制,c++14
    int b{ 012 }; //8进制
    int c{ 0xF }; //16进制
    std::cout << a << '\n'<< b <<'\n'<< c <<'\n';

    //转2进制,打印二进制形式
    std::bitset<8> bin1{ 0b1100'0101 }; //2进制,从右边起打印8位
    std::bitset<8> bin2{ 0xC5 }; //16进制
    std::bitset<4> bin3{ 0b1010 }; //只打印右边4位
    std::bitset<8> bin4{ 21 }; //10进制
    std::cout << bin1 << '\n' << bin2 << '\n' << bin3<<'\n'<<bin4<<'\n';

    //打印各种进制
    int x{ 12 };
    std::cout << x << '\n'; // 10进制(默认)
    std::cout << std::hex << x << '\n'; // 16进制
    std::cout << x << '\n'; // 注意,还是16进制
    std::cout << std::oct << x << '\n'; // 8进制
    std::cout << std::dec << x << '\n'; // 10进制
    std::cout << std::bitset<8>(x) << '\n'; //2进制

7、const(常量)、constexpr(编译时常量)、symbolic constants(符号常量)

【推荐】运行时常量(Runtime constants)用const。编译时常量(Compile-time constants)用constexpr。符号常量(symbolic constants)用constexpr,而不是 # define 宏

全局常量用 inline constexpr(c++17及以上)。

“magic numbers”魔幻数字,即代码中多次重复使用的数字,如系数。更改一处,而忘记更改其他地方,产生失误。

为了避免magic numbers,使用# define宏或constexpr来定义符号常量(给数字起个名字,用名字而不是直接用数字)。

# define宏debugger不可见、范围不可控、也不是正常的c++类型。因此使用constexpr。

#include <iostream>

//#define PI 3.14 //不推荐
constexpr double PI{ 3.14 };

int main()
{
    std::cout << "Enter your age:";
    int age;
    std::cin >> age;
    const int usersAge{age}; //运行时常量,运行之后才知晓
    std::cout << usersAge << '\n';

    constexpr double gravity{ 9.8 }; //编译时常量,直接初始化
    std::cout << gravity << '\n';

    std::cout << 2 * PI << '\n'; //符号常量PI

    return 0;
}

上述代码中 PI 是全局常量,推荐 inline constexpr。可以放到头文件里,如果全局常量很多,用于不同的逻辑,可以使用命名空间。

constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace constants //命名空间
{
    inline constexpr double PI{ 3.14 }; //全局常量
}

#endif

main.cpp

#include "constants.h" //引入自定义的
#include <iostream>

int main()
{
    std::cout << 2 * constants::PI << '\n'; //所在命名空间::PI

    return 0;
}

8、幂运算、除法

c++中无 ^ 运算符,使用cmath中的pow()函数。幂运算的结果都是double型

除法运算的结果类型是分子、分母中的高精度类型。

#include <iostream>
#include <cmath>  //std::pow
int main()
{
    std::cout << 3/2 << '\n' << typeid(3/2).name()<<'\n'; //int,1
    std::cout << typeid(3.0f / 2).name() << '\n'; //float,1.5
    std::cout << typeid(3.0 / 2).name() << '\n'; //double,1.5
    std::cout << std::pow(3, 2) <<'\n'<<typeid(std::pow(3,2)).name(); //double,9
    return 0;
}

9、条件运算符 ?:

格式:(condition) ? expression1 : expression2;

返回值:expression而不是value

注意:condition、整个三目运算,都要加上括号。

当“ : ”前后类型不一致时,使用if/else。

#include<iostream>

int main()
{
    std::cout << (3 > 5) ? 8: 9 <<'\n';        // 0,<< 优先级高于 ?:
    std::cout << ((3 > 5) ? 8 : 9) <<'\n';    // 9,推荐
    return 0;
}

10、浮点型的比较

判断大小,直接 > 或 <

判断是否相等(近似相等),用自定义函数(相等则返回1) approximatelyEqualAbsRel(d1, d2, 1e-12, 1e-8)

#include <iostream>
#include <algorithm> //std::max

bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
    // Check if the numbers are really close -- needed when comparing numbers near zero.
    double diff{ std::abs(a - b) };
    if (diff <= absEpsilon)
        return true;

    // Otherwise fall back to Knuth's algorithm
    return (diff <= (std::max(std::abs(a), std::abs(b)) * relEpsilon));
}

int main()
{    
    double d1{ 100.0 - 99.99 }; // 加断点调试,结果如上图
    double d2{ 10.0 - 9.99 }; 

    if (d1 == d2)
        std::cout << "d1 == d2" << '\n';
    else if (d1 > d2)
        std::cout << "d1 > d2" << '\n'; //输出"d1 > d2",而不是"d1 == d2"
    else if (d1 < d2)
        std::cout << "d1 < d2" << '\n';

    std::cout << approximatelyEqualAbsRel(d1, d2, 1e-12, 1e-8) << '\n';
    return 0;
}

11、与、或、非、异或

与、或、非:&&、||、!

c++没有异或运算符,使用 !=

if (a != b) ... // a XOR b, assuming a and b are Booleans
if (static_cast<bool>(a) != static_cast<bool>(b) != static_cast<bool>(c) != static_cast<bool>(d)) ... // a XOR b XOR c XOR d, for any type that can be converted to bool

12、位运算

左移、右移、与、或、非、异或:<<、>>、&、|、~、^

位赋值操作符:<<=、>>=、&=、|=、^=

注意:异或^时1的个数为奇数,则为1

  

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<4> x{ 0b1100 }; //4位二进制

    std::cout << x << '\n';
    std::cout << (x >> 1) << '\n'; // 右移1位,0110
    std::cout << (x << 1) << '\n'; // 左移1位,1000
    std::cout << (x <<= 1) << ' ' << x << '\n'; //左移1位并赋给x,1000

    std::cout << (std::bitset<4>{ 0b0101 } & std::bitset<4>{ 0b0110 }) <<'\n';
    std::cout << (std::bitset<4>{ 0b0101 } | std::bitset<4>{ 0b0110 }) <<'\n';
    std::cout << std::bitset<4>{ ~0b0101u } << '\n';
    std::cout << (std::bitset<4>{ 0b0111 } ^ std::bitset<4>{ 0b0011 } ^ std::bitset<4>{ 0b0001 })<<'\n';

    return 0;
}

13、位操作(使用位掩码bit masks+位运算)、位与RGBA图

把 0000‘0101 看成PLC的一排端子,检查某一位是开(0)还是闭(1),或者开、闭某一位或多位,此过程就是位操作。

注意:bitset<8>位运算后依然是二进制,uint_fast8_t位运算后是十进制

    std::bitset<8> a{ 0b0000'0101 };
    std::bitset<8> b{ 0b0000'0100 };
    std::cout << (a & b) << '\n';            //二进制,0000'0100

    std::uint_fast8_t flags{ 0b0000'0101 };
    std::cout << (flags & mask2) << '\n';    //十进制,4

① 检查某一位的开闭状态,(a & mask) ? 状态1 : 状态2 ;

#include <iostream>

int main()
{
    constexpr std::uint_fast8_t mask0{ 0b0000'0001 }; // represents bit 0
    constexpr std::uint_fast8_t mask1{ 0b0000'0010 }; // represents bit 1
    constexpr std::uint_fast8_t mask2{ 0b0000'0100 }; // represents bit 2 
    constexpr std::uint_fast8_t mask3{ 0b0000'1000 }; // represents bit 3
    constexpr std::uint_fast8_t mask4{ 0b0001'0000 }; // represents bit 4
    constexpr std::uint_fast8_t mask5{ 0b0010'0000 }; // represents bit 5
    constexpr std::uint_fast8_t mask6{ 0b0100'0000 }; // represents bit 6
    constexpr std::uint_fast8_t mask7{ 0b1000'0000 }; // represents bit 7

    std::uint_fast8_t flags{ 0b0000'0101 };

    std::cout << "第0位的开闭状态 " << ((flags & mask0) ? "闭\n" : "开\n");
    std::cout << "第1位的开闭状态 " << ((flags & mask1) ? "闭\n" : "开\n");

    return 0;
}

② 让某1位或多位变为1,a |= mask 或者 a |= (mask0 | mask1 | ...)

std::cout << "让第3位为1 " << std::bitset<8>(flags |= mask3) << '\n'; //0000'1101
std::cout<<"让第4、5位为1 "<<std::bitset<8>(flags |= (mask4|mask5)) << '\n'; //0011'1101

③ 让某1位或多位变为0,a &= ~mask 或者 a &= ~(mask0 | mask1 | ...)

std::cout<< "让第3位为0 " << std::bitset<8>(flags &= ~mask3) << '\n';//00110101
std::cout << "让第4、5位为0 " << std::bitset<8>(flags &= ~(mask4 | mask5)) << '\n'; //00000101

④ 切换位状态,a ^= mask 或者 a ^= (mask0 | mask1 | ...)

【推荐】操作单独位用函数 test, set, reset, flip,操作多位用位掩码。注意类型 std::bitset<8>

#include<bitset>
#include <iostream>

int main()
{
    constexpr std::bitset<8> mask0{ 0b0000'0001 }; // represents bit 0
    constexpr std::bitset<8> mask1{ 0b0000'0010 }; // represents bit 1
    constexpr std::bitset<8> mask2{ 0b0000'0100 }; // represents bit 2 
    constexpr std::bitset<8> mask3{ 0b0000'1000 }; // represents bit 3
    constexpr std::bitset<8> mask4{ 0b0001'0000 }; // represents bit 4
    constexpr std::bitset<8> mask5{ 0b0010'0000 }; // represents bit 5
    constexpr std::bitset<8> mask6{ 0b0100'0000 }; // represents bit 6
    constexpr std::bitset<8> mask7{ 0b1000'0000 }; // represents bit 7

    std::bitset<8> flags{ 0b0000'0101 };

    std::cout << flags.test(2) << '\n'; //检查第2位的状态,1
    std::cout << flags.set(3) << '\n'; //设置第3位为1,0000'1101
    std::cout << flags.reset(3) << '\n'; //设置第3位为0,0000'0101
    std::cout << flags.flip(4) << '\n'; //切换第4位的状态,0001'0101

    //操作多位,参考②③④,用std::bitset<8>类型

    return 0;
}

any函数,(a & b).any(); 判单a、b是否完全一致,一致返回1(true),否则0

*************************************** 位与RGBA图 *********************************************

RGBA 4个通道,每个通道0~255(8bit),所以RGBA图需32bit,分布如下:

 当颜色为 FF7F3300 时,R、G、B、A分别为 FF、7F、33、00

#include <iostream>

int main()
{
    constexpr std::uint_fast32_t redBits{ 0xFF000000 }; //16进制对应的2进制:高8位都是1,之后24位都是0
    constexpr std::uint_fast32_t greenBits{ 0x00FF0000 };
    constexpr std::uint_fast32_t blueBits{ 0x0000FF00 };
    constexpr std::uint_fast32_t alphaBits{ 0x000000FF };

    std::cout << "Enter a 32-bit RGBA color value in hexadecimal (e.g. FF7F3300): ";
    std::uint_fast32_t pixel{};
    std::cin >> std::hex >> pixel; // std::hex allows us to read in a hex value

    std::uint_fast8_t red = ((pixel & redBits) >> 24); //与运算、右移24位,只剩高8位
    std::uint_fast8_t green = ((pixel & greenBits) >> 16); //与运算、右移16位,从右往左取8位(uint_fast8_t)
    std::uint_fast8_t blue = ((pixel & blueBits) >> 8);
    std::uint_fast8_t alpha = (pixel & alphaBits);

    std::cout << "Your color contains:\n";
    std::cout << std::hex; // 之后以16进制打印
    std::cout << static_cast<int>(red) << " red\n"; //转10进制,以16进制打印
    std::cout << static_cast<int>(green) << " green\n";
    std::cout << static_cast<int>(blue) << " blue\n";
    std::cout << static_cast<int>(alpha) << " alpha\n";

    return 0;
}

14、内部连接(internal linkage)和外部连接(external linkage)

內连、外连是变量、函数的一种属性。只能被本cpp使用的,就是內连。可以被其他cpp识别使用的,就是外连。
static、const、constexpr修饰的变量、函数,会变成內连的。const、static一起使用会报错。
const、constexpr修饰的,可以使用extern关键字让其变为外连(注意extern constexpr无意义,constexpr修饰的变量必须初始化)。
一般的函数,默认外连。

//    a.cpp:
extern const int g_x { 3 };
extern constexpr int g_y { 4 };

//    main.cpp:
extern const int g_x;
//extern constexpr int g_y; //报错,所以extern constexpr无意义
 
int main()
{
    std::cout << g_x; //3
 
    return 0;
}

15、避免使用非常量全局变量(non-const global variables)

c++不允许放飞自我的存在(不安全),而是要带着镣铐舞蹈。全局变量已经放飞自我了,所以需要限制。
全局变量意味着粘连,会削弱模块化和灵活性。
💗 限制手段:让其变为內连(internal linkage)、放到命名空间里。
💗 访问手段:可外连的函数(external linkage)

#include <iostream>

namespace constants //命名空间
{
    constexpr double g_gravity{ 9.8 }; //內连
}
double instantVelocity(int time, double g_gravity) //访问函数,让其成为参数
{
    return g_gravity * time;
}
double getGravity() //访问函数
{
    // …… 可加一些逻辑处理
    return constants::g_gravity;
}
int main()
{
    std::cout << instantVelocity(5, constants::g_gravity);
}

16、static 局部变量(内存只被创建一次)

static修饰局部变量,会让变量由 automatic duration 变为 static duration。

automatic duration(自动持续时间),出了声明其的{ }就消亡。

static duration(静置时间),变量(该内存只会被创建一次,程序结束才消亡。

static修饰局部变量,会延长其生命周期,由{ }变为 整个程序。

static修饰全局变量,由外连变为內连(只被声明它的file内部使用)。

#include <iostream>

class MyClass
{
public:
    MyClass() //构造函数
    {
        std::cout << "构造函数" << std::endl;
    };
    void say() //自定义函数
    {
        std::cout << "Hello" << std::endl;
    };
};
void func()
{
    static MyClass a; //静态,该内存只会创建一次。即构造函数只会被调用一次,因为已创建。
    a.say();
}

int main()
{
    func();
    func();

    return 0;
}

17、作用域、生命周期、连接性(Scope, duration, linkage)

🐏 block scope / local scope

🐏 global scope / file scope

🐮 automatic duration

🐮 static duration

🐮 dynamic duration

🐍 no linkage

🐍 internal linkage

🐍 external linkage

请参考标题链接。

18、类型别名(用using而不是typedef)

using pairlist_t = std::vector<std::pair<std::string, int> >; //推荐
//typedef std::vector<std::pair<std::string, int> > pairlist_t;

19、cin与getline

cin获取的是以空格或回车来分隔的缓存,无视多个空格或回车,当然一定会遗留1个\n在缓存中。

getline获取\n前的缓存,只要遇到回车(\n)就执行,即使\n前是空的。

如果先cin,再getline,那么getline是不会获取用户输入的,因为遇到了cin遗留的\n。

去除遗留的\n,可以使用 cin.ignore(32767, '\n');

#include <iostream>
#include <string> //for string

using namespace std;

int main()
{
    string myName;
    string myAge;
    cin>>myName;    //输入 xiao ming再按回车,缓存中有xiao、ming、\n
    cin>>myAge;     //cin会从缓存中获取值,ming被取出。不会等待用户输入。

    cout << myName << endl; //xiao
    cout<<myAge<<endl;      //ming
    cin.ignore(32767,'\n'); //去除缓存中遗留的\n

    string yourName;
    string yourAge;

    getline(cin,yourName);
    getline(cin,yourAge);
    cout<<yourName<<yourAge;

    return 0;
}

20、enum、enum class

enum 类型不安全:

① 其枚举成员的作用域是整个命名空间,而不是枚举名范围(失控啦,枚举名无存在感)。

② 同一命名空间中直接访问枚举成员,且枚举成员唯一(已存在不可重复定义)。

③ 枚举成员会自动被隐式转换为int型(依次默认0、1、2、…)。

enum class 类型安全:

① 其枚举成员的作用域是所属枚举范围,通过 枚举名::枚举成员 访问。

② 枚举成员未被转换为int型,显示转换用static_cast<int>( )。

#include <iostream>

enum Color  //类型不安全,其枚举成员在作用域中(同一命名空间)不可再次定义。
{
    COLOR_RED,  //枚举成员,同一命名空间中可直接使用
    COLOR_BLUE
};
enum Fruit
{
    //COLOR_RED   //错误,因为同一命名空间中已有 COLOR_RED
    FRUIT_APPLE,
    FRUIT_BANANA
};

enum class Color2   //类型安全
{
    red,    //枚举成员,只能通过Color2::访问
    blue    //因为已有所属,无需带COLOR_这样的前缀
};

int main()
{
    Color color { COLOR_BLUE }; //直接用
    std::cout<<color<<'\n';     //已被隐式转换为int型

    Color2 color2 { Color2::red };  //通过::使用
    std::cout<<static_cast<int>(color2) <<'\n'; //必须显示转换

    return 0;
}

21、struct

结构体可以作为函数的参数进行传递,避免多个参数的逐一传递。

结构体也可以嵌套结构体。

结构体成员赋值,推荐统一赋值(推荐c++14及以上使用)。

struct Rectangle
{
     // 非静态成员初始化
    double width;   //默认0.0 
    double height{1.0};
};

int main()
{
    Rectangle rect1;
    rect1.width=2.0; //单独赋值
    rect1.height=2.0;

    Rectangle rect2{ 2.0, 2.0 }; // 统一赋值,建议c++14及以上使用

    return 0;
}
struct Employee
{
    short id;
    int age;
    double wage;
};
 
struct Company
{
    Employee CEO; // 嵌套结构体
    int numberOfEmployees;
};
 
int main()
{
    Company myCompany{{ 1, 42, 60000.0 }, 5 }; //统一赋值
    myCompany.CEO.age=50; //单独赋值
    myCompany.numberOfEmployees=10; //单独赋值

    return 0;
}

22、随机数生成

方式一:std::srand()指定起始种子,std::rand()生成随机数。

#include <iostream>
#include <cstdlib> // for std::rand() and std::srand()
#include <ctime> // for std::time()
int main()
{
    std::srand(static_cast<unsigned int>(std::time(nullptr))); //以时间作为种子
    std::cout << std::rand() << '\t'; //生成随机数

    return 0;
}

方式二:生成范围内的随机数,Mersenne Twister随机数

#include <iostream>
#include <random> // for std::mt19937
#include <ctime> // for std::time()
int main()
{
    std::mt19937 mersenne{ static_cast<std::mt19937::result_type>(std::time(nullptr)) }; //以时间作为种子
    std::uniform_int_distribution die{ 1, 6 };//均匀分布在[a,b]内的随机整数,c++17及以上
    // std::uniform_int_distribution<> die{ 1, 6 }; //c++17以下使用
    
    std::cout << die(mersenne) << '\t'; //生成随机数

    return 0;
}

方式三:第三方库 Effolkronium’s random library

只需下载 include/effolkronium里的 random.hpp,放到工程目录中即可。具体教程参考GitHub。

#include <iostream>
#include"random.hpp" //第三方库 Effolkronium’s random library

using Random=effolkronium::random_static; //别名
int main()
{
    std::cout<<Random::get(1,6)<<'\n'; //[1,6]内的随机整数
    std::cout<<Random::get(1,6)<<'\n';
    std::cout<<Random::get(1,6)<<'\n';

    return 0;
}

23、Arrays以及常用操作、动态数组

enum可以用字符代表整型数据,更形象。struct让属性集合到一起,面向对象编程。数组可以让同类型(int、double、float、char、string)的集合到一起。

std::size() 元素个数

sizeof() 数组大小,数组很容易退化为指针,所以经常是4字节。如作为函数参数使用后。当将整个结构或类传递给函数时,作为结构或类一部分的数组不会衰减。

#include <iostream>
#include<string>

int main()
{
    int intArray[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
    std::cout << "元素个数 " << std::size(intArray) << '\t'<< "数组大小 "<<sizeof(intArray);

    std::string strArray[]{"hello","kitty"};
    std::cout << strArray[1];
}

 

#include <iostream>
 
namespace Animals //让枚举元素有归属
{
    enum Animals // enum后的名字是无意义的
    {
        chicken,
        dog,
        cat,
        elephant,
        duck,
        snake,
        max_animals   //记录个数
    };
}
 
int main()
{
    int legs[Animals::max_animals]{ 2, 4, 4, 4, 2, 0 };
 
    std::cout << "An elephant has " << legs[Animals::elephant] << " legs.\n";
 
    return 0;
}

多维数组

    int arrayInt[3][5] //arrayInt[][]不指定大小,动态数组
    {
        { 1, 2, 3, 4, 5 },        // row 0
        { 6, 7, 8, 9, 10 },        // row 1
        { 11, 12, 13, 14, 15 }    // row 2
    };

常用操作:

交换——std::swap

排序——std::sort

查找是否有某个元素——std::find,返回该元素所在地址,无则返回最后地址

统计满足要求的元素个数——std::count_if,需与函数搭配,可代替for循环

#include <iostream>
#include<algorithm> //for std::sort

int main()
{
    int x{ 2 };
    int y{ 4 };
    std::swap(x, y); //交换
    std::cout << x << ' ' << y;

    int intArray[]{ 1, 1, 2, 30, 5, 8, 13, 21 };
    std::sort(std::begin(intArray),std::end(intArray)); //排序

    for (int i = 0; i < std::size(intArray); i++)
    {
        std::cout << intArray[i] << '\t';
    }
}
#include <iostream>
#include<algorithm> //for std::count_if

bool isEven(int x)
{
    return x % 2 == 0 ? true : false;
}

int main()
{
    int arrayInt[]{ 1,2,3,4,5 };
    std::cout << std::count_if( std::begin(arrayInt), std::end(arrayInt), isEven )<<'\n'; //count_if替代for循环

    int *ptr{ nullptr };
    ptr = std::find(std::begin(arrayInt), std::end(arrayInt), 4); //查找是否有4,返回4所在的地址,此时指针指向4的位置
    if (ptr != std::end(arrayInt)) //若存在则输出,若不存在则指针指到了最后
    {
        std::cout << (*ptr) << '\n';
    }
    else
    {
        std::cout << "数组中不存在" << '\n';
    }

    return 0;
}

【动态数组 dynamic array】

两种数组:fixed array、dynamic array

    int fixedArray[3]{}; //默认0
    //int fixedArray[]{ 0,0,0 }; // 同上

    int *dynamicArray{ new int[3]{} }; //new开辟的数组

fixed array是编译时的量,长度必须给定。

dynamic array是运行时的量,可以运行后给定。

#include <iostream>
#include <cstddef> // std::size_t

int main()
{
    std::cout << "请输入一个正整数";
    std::size_t length{}; //非负建议用 std::size_t 类型,即 usigned int
    std::cin >> length;
    int *array{ new int[length] {} }; //指针array指向一片连续的内存(即数组)。new开辟的数组大小无法通过std::size\sizeof得到。
    array[0] = 5;

    delete[] array; //释放new开辟的动态数组内存
    //不必 set array to nullptr/0 因为出其作用域即消亡

    return 0;
}

 

#include <cstddef> // std::size_t
#include <iostream>
#include <limits> // std::numeric_limits
#include <string>

std::size_t getNameCount()
{
    std::cout << "你想输入几个人的名字";
    std::size_t length{};
    std::cin >> length;

    return length;
}

void getNames(std::string *names, int length) //传入指向动态数组的指针,见main函数
{    
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略cin中遗留的 \n

    for (int i{ 0 }; i < length; ++i)
    {
        std::cout << "Enter name #" << i + 1 << ": ";
        std::getline(std::cin, names[i]);
    }
}

void printNames(std::string* names, int length)
{
    std::cout << "\nHere is your sorted list:\n";

    for (int i{ 0 }; i < length; ++i)
        std::cout << "Name #" << i + 1 << ": " << names[i] << '\n';
}

int main()
{
    std::size_t length{ getNameCount() };
    auto *names{ new std::string[length]{} }; //指针指向new出的动态数组内存

    getNames(names, length);
    printNames(names, length);

    delete[] names; //删除指向的数组内存

    return 0;
}

 

24、std::string与std::string_view

std::string会开辟内存,std::string_view不会开辟内存,像窗户一样看向某个内存(有种指针感觉的),内存变,则看向它的变量都变。

#include <iostream>
#include <string>
#include <string_view> //for std::string_view

int main()
{
    std::string str1{ "hello" }; //开辟内存str1,存储hello
    std::string_view str2{ str1 }; //看向str1
    std::string_view str3{ str2 }; //也看向str1
    //std::string str2{ str1 }; //触发拷贝,str2有自己的内存
    //std::string str3{ str2 }; //触发拷贝,str3有自己的内存,str1改变,不影响str2\str3

    std::cout << str3 << '\n';
    str1[0] = 'f'; //h改为f
    std::cout << str3 << '\n'; //fello

    return 0;
}

常用操作

    std::string_view str{ "Trains are fast!" };
    str.length(); //16
    str.substr(0, str.find(' ')); //截取字符串,从0开始到第一个空格位置。Trains
    std::cout << (str == "Trains are fast!") << '\n'; //是否相同。1
    str.remove_prefix(1); //删除头1个字符,有种窗户变小的感觉。
    str.remove_suffix(2); //删除后2个字符
    std::cout << str << '\n'; // rains are fas
    //Since C++20
    //std::cout << str.starts_with("Boats") << '\n';    //0
    //std::cout << str.ends_with("fast!") << '\n';    //1

25、指针

指针是保存内存地址的变量。

32位可执行程序中大小是4字节,64位是8字节。

初始化指针建议使用nullptr进行初始化。

指针的作用:

① 数组是使用指针实现的。指针可用于遍历数组(作为数组索引的替代方法)
② 在c++中动态分配内存的唯一方法
③ 更高效,以不涉及复制数据的方式向函数传递大量数据
④ 可将一个函数作为参数传递给另一个函数
⑤ 在处理继承时,它们可用于实现多态性
⑥ 它们可以使一个结构/类指向另一个结构/类,从而形成链。这在一些更高级的数据结构中很有用,比如链表和树。

#include <iostream>

int main()
{
    int v{ 5 };
    //int *ptr{ &v }; //同下等效
    int *ptr{ nullptr };
    ptr = &v;    //指针即地址
    *ptr = 6;    //*ptr即值
    std::cout << typeid(&v).name() << '/n'; //取地址返回的是指针,int *

    return 0;
}

26、内存分配的3种方式、避免dangling pointer、new失败、内存泄漏

【内存分配的3种方式】

① Static memory allocation,static and global variables只被分配一次,生命贯穿整个项目周期。
② Automatic memory allocation,function parameters and local variables,出所在block即被收回。在stack栈内存。
③ Dynamic memory allocation,使用new、delete。在heap堆内存。

①②共同点:
变量或数组(此处是固定数组fixed arrays)的大小,在编译时必须已知。
内存会自动回收。

stack栈内存,通常非常小,Visual Studio默认堆栈大小为1MB。如果超过这个数字,将导致堆栈溢出,操作系统可能会关闭程序。most normal variables (including fixed arrays)就是在此内存上。
heap堆内存,可达到GB,用new、delete操作。

【避免dangling pointer】

dangling pointer(空悬指针;指针挂起;野指针)
① 尽量避免让多个指针指向同一块动态内存。或者,明确哪个指针拥有内存(并负责删除它)和哪个只是访问它。
② delete ptr;ptr=nullptr; (注意在指针失控之前使用。如在出int *ptr所在大括号之前使用)

【new失败】此知识点看看就可以🙂

为了防止new分配内存失败(概率极低)而导致程序崩溃或抛出异常,使用std::nothrow让new返回一个空指针。

#include <iostream>

int main()
{
    int *value{ new (std::nothrow) int }; //用法,放在new与类型之间
    if (!value)
    {
        //异常处理
        std::cout << "内存分配失败";
    }
    delete value;
    value = nullptr;

    return 0;
}

【内存泄漏】

计算机是通过地址来操作内存的。当该内存的地址丢失时,内存就无法被使用但仍让存在,这就是内存泄漏。

void doSomething()
{
    int *ptr{ new int{} }; //分配一块整型内存,并有指针指向它
}

pointers variables are just normal variables,即指针也遵循局部变量的作用域规则。
上述代码中ptr是唯一保存动态分配的整数地址的变量,当ptr被销毁时,地址就丢了,就不会再有对动态分配的内存的引用了,但内存依然存在。

内存泄漏的情况:
指针超出了作用域;
持有动态分配内存的地址的指针被分配另一个值。(应该在指向新内存之前,delete掉旧内存)

#include <iostream>

int main()
{
    int value = 5; //自动分配内存1
    int* ptr{ new int{} }; // 动态分配内存2,地址由ptr保存
    // delete ptr; //加上此句,就稳了
    ptr = &value; // ptr变心了,指向了内存1,内存2泄漏

    return 0;
}

27、const修饰的指针引用

 *之前是指针指向的目标,*之后是指针本身的属性(即地址是否可变)

引用一旦初始化,就不能再引用其他。
引用通常被用作函数参数,避免复制,效率高。若不想改变实参,用const修饰。

与指针类似:
int &ref 只能变量来初始化。
const int &ref 变、常量都可以初始化,但是会被当做常量。只能用之,不能改之。

28、for-each 循环

推荐&引用,更高效。它应用于类似列表的结构,例如 fixed array, std :: vector, linked lists, trees, and maps。

#include <iostream>

int main()
{
    std::string array[]{ "peter", "likes", "frozen", "yogurt" };
    //for (auto element : array) // element会拷贝array中当前元素
    //for (auto &element : array) //效率更高,但可能改变array中元素
    for (const auto &element : array) //只用不改
    {
        std::cout << element << ' ';
    }

    return 0;
}

注意,for-each对指向数组的指针无效,对动态数组也无效。

原因:为了遍历数组,for-each需知数组的大小,而已经衰减为指针的数组无法获知大小,所以不起作用。动态数组大小也未知。

#include <iostream>

int sumArray(const int array[]) // array为指针
{
    int sum{ 0 };
    for (auto number : array) // 错误,array大小未知
    {
        sum += number;
    }
    return sum;
}

int main()
{
    constexpr int array[]{ 9, 7, 5, 3, 1 };
    std::cout << sumArray(array) << '\n'; // array衰减为指针

    return 0;
}

29、void pointer 与 null pointer 

void pointer(不推荐使用)可以指向任何类型,但是不知道类型具体是什么。空指针必须显式转换为它指向的类型才能做其他使用。

null pointer (即 nullptr )不指向地址的指针。

#include<iostream>
#include<string>
int main() 
{
    int value{ 5 };
    std::string str{ "hello" };
    void *voidPtr{ nullptr }; //null pointer
    voidPtr = &value; //指向value
    std::cout << *static_cast<int*>(voidPtr) << '\n'; //转int*后再使用
    voidPtr = &str; //指向str
    std::cout << *static_cast<std::string*>(voidPtr) << '\n';

    return 0;
}

30、指向指针的指针、多维动态数组

#include<iostream>
int main() 
{
    int value{ 5 };
    int *ptr{ &value };
    int **ptrPtr{ &ptr }; //不可以 int **ptrPtr{ &&value };
    std::cout << **ptrPtr << '\n';

    return 0;
}

指向指针的指针,一般用于分配动态指针数组、动态多维数组。

int **array = new int*[10]; //包含10个整型指针

动态多维数组的创建和释放需要借助循环体。

    int **array = new int*[10]; // 包含10个整型指针的数组 — rows
    for (int count = 0; count < 10; ++count)
        array[count] = new int[5]; // 每个指针指向包含5个整型元素的数组 - columns
    array[9][4] = 3; //赋值

    for (int count = 0; count < 10; ++count)
        delete[] array[count]; //先删除指针元素
    delete[] array; // 再删除最外层指针

31、std::array std::vector

推荐使用 std::array and std::vector的理由:
固定数组经常会衰变成指针,这样就会丢失数组长度信息。
动态数组会有混乱的分配问题,很难不出错地调整大小。
内存在超出作用域时自动释放,无需手动delete释放。

【std::array】

推荐std::array代替固定数组。

传递std::array类型的数组,推荐使用(常)引用。

std::array<int, 3> ar; //c++11,<类型,个数> 缺一不可
std::array ar2{ 1,2,3 }; //c++17,可以省略<int,3>但是必须显式地初始化。
//std::array ar2; //错误

ar[2] = 5;
ar.at(0) = 6; //at有下标越界检查功能,更安全但性能降低。
#include <algorithm> // for std::sort
#include <array>
#include <iostream>

int main()
{
    std::array myArray{ 7, 3, 1, 9, 5 };
    std::cout << myArray.size() << '\n'; //5
    std::sort(myArray.begin(), myArray.end()); // 升序
//  std::sort(myArray.rbegin(), myArray.rend()); // 降序

    for (int element : myArray)
        std::cout << element << ' ';

    std::cout << '\n';

    return 0;
}

【std::vector】 

std::array 创建 fixed array
std::vector 创建 dynamic array

std::vector比std::array更强大,是c++工具箱中最有用和最通用的工具之一。

内置的动态数组(指针new实现)不知道它们所指向的数组的长度,std::vector可以跟踪长度,用size()函数获取。

长度的增大与缩小使用resize()实现。增大以相应类型的默认值补齐,缩小则直接丢掉多余的元素。

#include <iostream>
#include <vector>

int main()
{
    //std::vector<int> array(5); //包含5个元素的向量,5个0
    std::vector array{ 0, 1, 2 }; //注意()与{}区别。
    array.resize(5); // set size to 5

    std::cout << "The length is: " << array.size() << '\n'; //5

    for (int i : array)    //0 1 2 0 0
        std::cout << i << ' ';

    std::cout << '\n';

    return 0;
}

32、std::find、std::find_if、std::count、std::count_if、std::sort、std::for_each

std::find、std::find_if

查找某个值、或者满足一定条件的项,返回第一次匹配的位置或者最后元素+1的位置(即end)。std::find_if 需搭配bool函数使用。

auto ptr{ std::find(arr.begin(), arr.end(), 具体的值) }; 
auto ptr{ std::find_if(arr.begin(), arr.end(), 自定义bool函数) };

std::count、std::count_if

统计某个值、或者满足一定条件的项的个数。用法同上。

#include <algorithm>
#include <array>
#include <iostream>
#include <string_view>

bool containsNut(std::string_view str) //是否包含nut字符串
{
    // 注意,此find()是std::string_view::find里的。
    // 若无匹配项,返回std::string_view::npos
    // 若有,返回第一个匹配的元素位置
    return (str.find("nut") != std::string_view::npos); 
}

int main()
{
    std::array<std::string_view, 5> arr{ "apple", "banana", "walnut", "lemon" ,"peanut" };
    auto ptr{ std::find_if(arr.begin(), arr.end(), containsNut) }; // 查找包含"nut"的第一个元素

    if (ptr == arr.end())
    {
        std::cout << "No nuts\n";
    }
    else
    {
        std::cout << "Found " << *ptr << '\n';
    }

    return 0;
}

std::sort

std::sort(arr.begin(), arr.end());   //升序
std::sort(arr.rbegin(), arr.rend()); //降序

std::for_each 对元素做相同操作时(如扩大2倍),可以使用。需搭配函数。

#include <algorithm>
#include <array>
#include <iostream>

void doubleNumber(int &i)
{
    i *= 2;
}

int main()
{
    std::array arr{ 1, 2, 3, 4 };
    std::for_each(arr.begin(), arr.end(), doubleNumber); // 2 4 6 8
    //std::ranges::for_each(arr, doubleNumber); // C++20

    //std::for_each(std::next(arr.begin()), arr.end(), doubleNumber); // 1 4 6 8,从下一个位置开始。

    for (int i : arr)
    {
        std::cout << i << ' ';
    }
    std::cout << '\n';

    return 0;
}

 

posted @ 2020-06-23 10:57  夕西行  阅读(745)  评论(0编辑  收藏  举报