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
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、头文件
【推荐】
③ .h中使用如下,或者#pragma once(前者都兼容,此未必),防止引入冲突。
#ifndef ADD_H //头文件名全大写_H #define ADD_H int add(int x, int y); #endif
避免#include里带路径,应该在IDE里设置。
从下图可知,编译阶段识别声明(头文件),链接阶段才识别定义(cpp文件)。
4、变量
注意下图红框,不同系统不同位数不同编译器无法保证具体的字节数,只能保证最小字节。
浮点型存在 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;
}