C++学习笔记

01.vector的erase( )的返回值

返回的是一个迭代器,指向被删除元素的下一个位置。(迭代器指针的位置没变,其实是因为被删元素之后的元素向前移了)

如果删除元素之后没有元素了,直接返回尾迭代器


【example 1 start】指定一个元素,把vector中的这个元素全部删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 指定一个元素,vector中的这个元素全部删除
void deleteElement(vector<int>&vc,int element)
{
auto it = vc.begin();
while(it!=vc.end()){
if(*it==element){
it = vc.erase(it); // 更新循环变量 重点
continue;
}
it++;
}
}

int main(int argc,char* argv)
{
vector<int>vc{1,1,2,3,4,1,2,1,5,6,7,8,9,10};
cout<<"Original elements------>";
for_each(vc.begin(),vc.end(),[&](const int&ele){
cout<<ele<<" ";
});
cout<<endl;
deleteElement(vc,1);
cout<<"After delete element 1------->";
for(auto& x:vc){
cout<<x<<" ";
}
return 0;
}

Running this code:

【example 1 end】

02.static关键字

用static修饰的东西就是一个全局变量,但是会有作用域限制(“私有的全局变量”)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void function(){
static int x;
int static y;
}
// 其中x、y就是为function所“私有的”全局变量

class Dog{
private:
int x = 100;//C++11支持这样
int y;
static int z;//z为Dog类所“私有的”全局变量 访问权限是private
static const int a = 100;// 带有类内初始值设定项的成员必须为常量
public:
static int name;//name为Dog类所“私有的”全局变量 访问权限是public
}// z、a、name不是Dog的成员,没有任何“血缘关系” 访问权限对别人隐藏,对本类开放
// 全局变量的初始化
int Dog::z = 100;

从文件的角度来看,用static修饰的东西只能在本文件内使用

1
2
3
4
5
6
//test.cpp
static void show();
//show 只能给自己的cpp使用,在别的cpp中不能使用,即使你加了extern修饰
//所以当你想把本文件中的某个东西给别人用时,就不要加上static修饰,而且要对于变量要加上extern修饰,对于函数可加可不加

//main.cpp

static应用:单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CSingleton {
private:
CSingleton(){}
static CSingleton* m_pInstance;
public:
~CSingleton() {// 析构函数不能私有
if (m_pInstance != nullptr) {
delete m_pInstance;
}
}
static CSingleton* getInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new CSingleton();
}
return m_pInstance;
}
};
CSingleton* CSingleton::m_pInstance = nullptr; // 静态成员初始化

03.a对b真正的余数

1
r = ( a%b + b) % b;

04.C++构造函数初始化列表中不能使用this指针

  • this指针属于对象,初始化列表在构造函数之前执行,在对象还没有构造完成前,使用this指针,编译器无法识别。

  • 构造函数中的内容,属于assignment,并不是初始化。

  • 在初始化构造列表中,编译器可以区分和成员变量同名的函数参数

05.C++中输出一个对象

友元+重载<<

在重载输入输出运算符的时候,只能采用全局方式的方式(因为我们不能在ostream和istream类中编写成员函数),这才是友元函数的真正应用场景。对于输出运算符,主要负责打印输出对象的内容而非控制格式,不应该打印换行符;对于输入运算符,必须处理可能失败的情况(通常处理失败为默认的构造函数的形式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <string>
using namespace std;// 这行不可少
class Student {
string name;
int age;
public:
Student(){}
//在初始化构造列表中,编译器可以区分和成员变量同名的函数参数
Student(string name,int age):name(name),age(age){}
friend ostream& operator<<(ostream&out,const Student&stu) {// 最好加上const,否则const Student无法输出
out << "name=" << stu.name << " age=" << stu.age;
return out;
}
friend istream& operator>>(istream&in, Student&stu) {
in >> stu.name >> stu.age;
if (!in) {
stu = Student();
}
return in;
}
};

int main()
{
Student stu;
cout << "请输入学生姓名和年龄:" << endl;
cin >> stu;
cout << stu;
return 0;
}

06.priority_queue的优先级设置

  • greater<类名>表示数字小的优先级高

  • less<类名>表示数字大的优先级低

    ​ 队头-----------------------------------------------------------------队尾

greater即按升序排列: 最小的----------------------------------------------------------------最大的

less即按降序排列: 最大的----------------------------------------------------------------最小的

但是要求类中要重载operator<和operator>,必须并且按照正常的逻辑处理。

1
2
priority_queue<类名,vector<类名>,greater<类名>> q;
队列中放的是什么类型 底层比较承载容器 仿函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <string>
#include <stack>
#include <queue>
#include <algorithm>
using namespace std;

class Fruit {
string name;
int price;
public:
Fruit(){}
Fruit(string name,int price):name(name),price(price){}
friend istream& operator>>(istream&in,Fruit&fruit) {
in >> fruit.name >> fruit.price;
if (!in) {
fruit = Fruit();
}
return in;
}
friend ostream& operator<<(ostream&out, const Fruit&fruit) {// 最好加上const,否则对于const Fruit类型无法输出 打印时不做格式控制
out << fruit.name << " " << fruit.price;
return out;
}
bool operator<(const Fruit&ft) const{// 后面的const必须加上 否则编译不过
return this->price < ft.price;
}
bool operator>(const Fruit&ft) const {// 后面的const必须加上 否则编译不过
return this->price > ft.price;
}
};

int main()
{
Fruit apple;
Fruit pear;
Fruit strawberry;
cin >> apple;
cin >> pear;
cin >> strawberry;
priority_queue<Fruit, vector<Fruit>, greater<Fruit>> q1;
priority_queue<Fruit, vector<Fruit>, less<Fruit>> q2;
q1.push(apple);
q1.push(pear);
q1.push(strawberry);
q2.push(apple);
q2.push(pear);
q2.push(strawberry);
cout << q1.top() << endl;
cout << q2.top() << endl;
return 0;
}

07.map插入一个pair

1
2
3
4
map<string, int>ma;
ma.insert(map<string, int>::value_type("java", 20));
ma.insert(make_pair("C++", 80)); //使用make_pair()函数
ma.insert(pair<string,int>("Python", 10));//使用pair<类型1,类型2>

08.字符编码

1.将所有的字符映射成一个数字,这个数字是唯一的

2.这个数字在计算机中如何存储没有规定

Unicode只是一个字符集,它只规定了符号的二进制代码,却没有规定这个二进制代码在计算机中如何存储。

cmd命令窗口中,GBK代码页936,Unicode代码页65001。

1
chcp 65001

09.C++单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Object {
public:
static Object* getInstance();
private:
class GarbageCollector; // 垃圾回收机制
static Object *m_obj;
static GarbageCollector gc;
class GarbageCollector {
public:
~GarbageCollector() {
if (Object::m_obj) {
delete Object::m_obj;
Object::m_obj = nullptr;
}
}
};
private:
Object() {};// 隐藏构造函数
};
// 静态类型的初始化必须放在全局中
Object* Object::m_obj = nullptr;
Object::GarbageCollector Object::gc;
Object* Object::getInstance() {
if (m_obj == nullptr) {
m_obj = new Object();
}
return m_obj;
}

10.拷贝构造函数和operator= 的区别

  • 拷贝构造函数在一个对象刚刚开始出现的时候被调用 拷贝构造

  • operator= 是已经有了对象,并且用另外的对象给它赋值的时候被调用 拷贝赋值

1
2
3
4
5
6
7
8
9
10
11
12
class String 
{
public:
String(const char*cstr = 0);
String(const String& str); //拷贝构造 copy ctor
String& operator=(const String&str);//拷贝赋值 copy op=
~String(); //析构函数 Big Three
char* get_c_str()const { return m_data; }
private:
char* m_data;
};

  • 拷贝构造函数中直接开辟内存,因为是新建对象,不用考虑释放旧内存的问题, 直接开辟内存即可

  • 拷贝赋值函数中一定要检测是不是自我赋值,并且要先释放自己的内存(delete)、开辟新内存(new)、拷贝

11.常引用指向不同的数据时,会产生临时变量

常引用指向不同的数据时,会产生临时变量,即引用指向的并不是初始化的那个变量

1
2
3
4
int age = 20;
const long&refAge = age;
age = 30;
std::cout<<refAge<<std::endl;// 输出20

12.C++函数包装器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <functional>
// 函数包装器 T是数据类型 F是函数
template<typename T,typename F>
T run(T v, F f) {
return f(v);
}
template<typename T,typename F>
T run(T v1, T v2, F f) {
return f(v1,v2);
}

std::function<函数返回值(函数参数)>对象名字 = 函数地址
std::function<double(double)> func1 = [](double v){return v*2;};
func1(10.5);
run(10.5,func1);
std::function<int(int,int)> func2 = [](int v1,int v2){return v1+v2;};
func2(10,20);
run(10,20,func2);

13.C++前置声明解决模板二次编译问题

  • 类的前置声明

  • 友元模板函数的前置声明

  • 友元模板函数声明需要增加泛型的支持

声明和定义分别在不同的文件(模板函数、模板友元)或者尽量将模板函数和模板友元放在一个文件下。

  • 将类的声明与函数的声明写在.h文件中

  • 类的实现及函数的实现写在.hpp文件中

  • 调用者包含.hpp文件

Complex.h文件,

存放类的声明和函数的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

//类的前置声明
template <typename T>
class Complex;

//友元函数的前置声明
template <typename T>
ostream& operator<<(ostream &out, Complex<T> &c);

template <typename T>
class Complex {

//友元函数实现运算符重载
friend ostream& operator<< (ostream &out, Complex<T> &c);

public:
Complex(T a, T b);

//运算符重载+
Complex<T> operator+(Complex<T> &c);

//普通加法函数
Complex<T> myAdd(Complex<T> &c1, Complex<T> &c2);

private:
T a;
T b;
};

Complex.hpp文件

存放模板函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

#include "Complex.h"

//友元函数的实现
template <typename T>
ostream& operator<<(ostream &out, Complex<T> &c)
{
out<<c.a << " + " << c.b << "i";
return out;
}


//函数的实现
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}

template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c)
{
Complex temp(this->a + c.a, this->b + c.b);
return temp;
}

template <typename T>
Complex<T> Complex<T>::myAdd(Complex<T> &c1, Complex<T> &c2)
{
Complex temp(c1.a + c2.a, c1.b + c2.b);
return temp;
}


14.std::ifstream::rdbuf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// read file data using associated buffer's members
#include <iostream> // std::cout
#include <fstream> // std::filebuf, std::ifstream

int main () {
std::ifstream ifs ("test.txt", std::ifstream::binary);

// get pointer to associated buffer object
std::filebuf* pbuf = ifs.rdbuf();

// get file size using buffer's members
std::size_t size = pbuf->pubseekoff (0,ifs.end,ifs.in);
pbuf->pubseekpos (0,ifs.in);

// allocate memory to contain file data
char* buffer=new char[size];

// get file data
pbuf->sgetn (buffer,size);

ifs.close();

// write content to stdout
std::cout.write (buffer,size);

delete[] buffer;

return 0;
}

15.rdbuf()简单实现文件拷贝

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

std::ifstream in("./source.txt",std::ios::binary);
std::ifstream out("./copy.txt",std::ios::binary);
out<<in.rdbuf();

//std::stringstream ss;
//ss<<in.rdbuf();
//std::string s = ss.str(); // std::stringstring 转 std::string
//const char* cs = s.c_str(); // std::string 转 const char*

简要的理解:rdbuf() 返回“一个水流”,需要把它接(<<)到一个流对象上. 譬如stringstream.

16.C++对二进制文件的读写

  • 打开文件时必须指明std::ios::binary

  • 流.read() 流.write() 其参数都是(char* buffer,size_t n) n表示字节数

  • 输入流.gcount() 返回实际读取的字节数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    std::ifstream in;
    std::ofstream out;
    in.open("./metadata.txt",std::ios::binary);
    out.open("./cop2.txt",std::ios::binary);
    const int buffer_size = 1024*8;
    char* buffer = new char[buffer_size];
    int n;

    while(in.peek()!=EOF){ // 判断流指针是否到达末尾
    in.read(buffer,buffer_size);
    n = in.gcount();
    out.write(buffer,n);
    }
    delete buffer;
    in.close;
    out.close();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// read file data using associated buffer's members
#include <iostream> // std::cout
#include <fstream> // std::filebuf, std::ifstream
#include <sstream>
#include <vector>
#include <string>
#include <ctime>

int main () {
std::cout<<"Program starting......"<<std::endl;
std::ifstream in;
std::ofstream out;
in.open("./metadata.txt",std::ios::binary);
out.open("./cop.txt",std::ios::binary);
clock_t start,end;
start = clock();
out<<in.rdbuf();
end = clock();
std::cout<<"first:"<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<std::endl;
in.close();
out.close();

in.open("./metadata.txt",std::ios::binary);
out.open("./cop2.txt",std::ios::binary);

const int buffer_size = 1024*8;
char* buffer = new char[buffer_size];
int n;
start = clock();
while(in.peek()!=EOF){
in.read(buffer,buffer_size);
n = in.gcount();
out.write(buffer,n);
}
end = clock();
std::cout<<"second:"<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<std::endl;
in.close();
out.close();
delete[] buffer;
std::cout<<"\nProgram finished!"<<std::endl;
return 0;
}

注意:读取大文件时,方式2要更快

17.随机文件访问

  • C++语言在文件流的基类中定义了几个函数进行文件定位

  • seekg() 、seekp() g-get -读 p-put-写

  • tellg()、tellp()

    1
    2
    3
    4
    ifstream in("./CMakeLists.txt",std::ios::in);
    in.seekg(0,std::ios::end);
    long n = in.tellg();
    std::cout<<"文件大小为"<<n<<"个字节!"<<std::endl;

18.STL中为什么将top()和pop()这两个函数分开?

  • pop()函数返回void

  • top()函数返回栈顶元素的引用

  • One might wonder why pop() returns void, instead of value_type. That is, why must one use top() and pop() to examine and remove the top element, instead of combining the two in a single member function? In fact, there is a good reason for this design. If pop() returned the top element, it would have to return by value rather than by reference: return by reference would create a dangling pointer. Return by value, however, is inefficient: it involves at least one redundant copy constructor call. Since it is impossible for pop() to return a value in such a way as to be both efficient and correct, it is more sensible for it to return no value at all and to require clients to use top() to inspect the value at the top of the stack.

19.C++: warning: C4930: prototyped function not called (was a variable definition intended?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
using std::cout;
using std::endl;
class Computer {
public:
Computer() {
cout << "Computer()" << endl;
}
~Computer() {
cout << "~Computer()" << endl;
}
};
class PC : public Computer {
public:
PC() {
cout << "PC()" << endl;
}
~PC() {
cout << "~PC()" << endl;
}
};
class Desktop : public PC {
public:
Desktop() { cout << "Desktop()" << endl; }
~Desktop() { cout << "~Desktop()" << endl; }
};
class Laptop : public PC {
public:
Laptop() { cout << "Laptop()" << endl; }
~Laptop() { cout << "~Laptop()" << endl; }
};

int main() {
PC d();
PC l();
std::cin.get();
return 0;
}

上述代码中:

1
2
PC d();
PC l();

编译器无法分辨你当前的代码是在声明一个函数原型,还是在调用一个函数。所以上述代码不会打印任何东西!

再看如下例题:

现有以下代码,则编译时会报错的是( )

  • A. 语句1

  • B. 语句2

  • C. 语句3

  • D. 语句4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Test
{
Test(int) {}
Test();
void fun() {}
};
int main()
{
Test a(1); //语句1
a.fun(); //语句2
Test b(); //语句3
b.fun(); //语句4
return 0;
}

答案: D Test b()这个语法相当于声明了一个函数,函数名为b,返回值为Test,传入的参数为空。但实际上,代码作者希望声明一个类型为Test,变量为b的变量,应该写成Test b,但程序这个错误在编译时是检测不出来的,出错的是语句4 b.fun()

20. C++中构造函数的继承

using 父类名字::父类名字

文章作者: 小王同学
文章链接: https://morvan.top/2015/10/01/C++Note/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 小王同学的精神驿站