Review of C++ Basics (Part 2)

Review of C++ Basics (Part 2)

foreword

I learned a little bit of C++ before, but I haven't used it for a long time. I read the book from the beginning and took brief notes so that I can review and study it later. This is the next article, the link to the previous article: Review of C++ Basics (Part 1)

There are four main forms of code reuse in the C++ language:

  1. function, the same code module can be called repeatedly
  2. Class, different objects of the same class share the same data structure and a set of operations
  3. Inheritance and polymorphism, derived classes can reuse base class code, and polymorphism ensures the flexibility of code reuse
  4. Template, the basis of generic programming, different instantiation versions share the same code design, and is the blueprint for creating classes and functions

Templates and Generic Programming

const int& getMax(const int &a,const int &b){
	return a>b ?a:b;
}
const string& getMax(const string &a, const string &b){
	return a>b?a:b;
} //两段代码中只有数据类型不同

When overloading a function in a class, different data types need to be redefined. If you can avoid the parameter type, you can fill it in when using the parameter type.

Function templates can solve the above problems

//getMax函数模板
template <typename T> //模板以关键字template开始,紧跟一个模板参数列表
const T& getMax(const T &a, const T &b){
	return a>b?a:b;
}

When using the above function templates, instantiation is required.

The parameter type of the template can be inferred or explicitly specified by itself

cout<<getMax(1.0,2.5)<<endl; //推断参数类型T为double
cout<<getMax<double>(1.0, 2.5)<<endl; //显式指明

class member template

Member functions of classes can also be defined as function templates

class X{
	void *m_p =nullptr;
public:
	templarte<typename T>
	void reset (T *t){m_p =t;} //成员函数reset定义为一个函数模板

};

variadic function template

template <typename...Args> //可变数目的参数称为参数包,用省略号“...”表示,可以包含0~任意个模板参数
void foo(Args ...args){
	cout<<sizeof...(args)<<endl; //打印参数包args中参数的个数
}
foo(); //0个参数,输出0
foo(1,1.5); //输出2
foo(1,1.5,"C++"); //输出3

class template

// 类模板的定义以关键字template开始,后跟模板参数列表
template<typename T, size_t N> //size_t是无符号整数,常用于数组的下标
class Array{
	T m_ele[N];
public:
	Array(){} //默认构造函数
	Array(const std::initializer_list <T> &); //initializer_list是支持有相同类型但数量未知的列表类型,这里没有写形参,应该是一个initializer_list 类型的引用常量
	T& operator[](size_t i);
	constexpr size_t size() {return N;}
};
//实例化类模板
Array<char,5>a;
Array<int,5>b ={1,2,3};

Dynamic Memory and Data Structures

Memory is allocated when the object is created and reclaimed when it goes out of scope. But sometimes the memory is used too much, and there is no need to allocate so much memory to the object, so the dynamic memory allocation technology comes in handy

dynamic memory

Dynamic objects are created in dynamic memory, also known as free storage or the heap .

new is used to allocate memory for creating dynamic objects, and delete is used to release dynamic memory.

int *pi =new int; //在堆中创建一个int类型的对象,把他的地址放到指针对象pi中
delete pi; //delete后跟一个指向动态对象的指针,用来释放动态对象的存储空间
pi = nullptr; //上面pi指向的内存已经被释放了,pi是一个空悬指针。通常是在delete之后重置指针指向nullptr

memory leak

In the process of use, it is necessary to avoid the situation that the memory that is no longer used cannot be released. This situation is also called memory leak.

int i,*q =new int(2); 
q =&i; //错误:发生内存泄漏

smart pointer

Used to solve problems such as dangling pointers or memory leaks

Three types of smart pointers:

1. unique_ptr 独占所指向的对象,采用直接初始化
2. shared_ptr 允许多个指针指向同一个对象,采用直接初始化
3. weak_ptr 一种不控制所指对象生命期的智能指针,指向shared_ptr所管理的对象

The three pointers are all class templates, defined in the memory header file.

{
unique_ptr <int> p2(new int(207)); //类模板,实例化为int类型的,采用直接初始化
}//p2离开作用域被销毁,同时释放其指向的动态内存,也就是说p2消亡时,其指向的对象也会消亡,动态内存自动释放。
shared_ptr<int> p1(new int(100)); //直接初始化
shared_ptr<int> p1=new int(100); //赋值初始化,错误,只能使用直接初始化

dynamic array

int n=5;
int *pa =new int[n]; //方括号中必须是整型,不必是常量,返回第一个元素的地址
delete [] pa; //释放内存,逆向销毁,首先销毁最后一个元素

data structure

linear linked list

Linear tables are contiguous both logically and physically. When accessing data randomly, it may be necessary to move a lot of data. A chain structure does not require logically adjacent elements to be physically adjacent.

A linear linked list is also called a singly linked list. Each data element occupies a node (node). A node contains a data field and a pointer field, where the address of the next node is stored in the pointer field.

Singly linked list structure

The single-linked list uses the pointer head to point to the first node of the single-linked list. Through the head pointer, each element can be traversed. The pointer of the last element points to empty, and the tail at the end points to the node at the end of the list.

There are generally push_back, earse, clear, insert and other member functions in the linked list.

chain stack

Stack (stack) can only be inserted and deleted at one end of the linear list.

Chain stack structure

In the stack, there are generally operations such as pushing, popping, clearing, and taking the top element of the stack.

binary tree

A non-empty tree has one and only one root node. The number of subtrees of each node is the degree of the node. For example, the degree of node B is 3. Nodes with a degree of 0 are called leaf nodes, such as D, E, H, etc.

tree structure

The definition of a binary tree is that the degree of each node does not exceed 2, but at least one node must have degree 2. The following is the structure of the binary tree.

binary tree

In a binary search tree, the data value of the left subtree of any node is smaller than the data value of the node, and the data value of the right subtree of any node is greater than or equal to the data value of the node.

Inheritance and polymorphism

inherit

Inheritance is one of the important means of code reuse. It is easy to define a new class that is similar to an existing class but not identical.

The inherited class is called the base class , and the resulting new class is called the derived class . If a derived class has only one base class, it is called single inheritance, and if it has more than one base class, it is called multiple inheritance.

Use a code example to explain the principle.

class Person {  //基类
protected:  //而由protected限定符继承的可以访问其中的受保护成员,但是在派生类外不可访问。
	string m_name;//名字
	int m_age;	//年龄
public:
	Person(const string &name = ", int age = 0) :m_name (name), m age(age){}
    virtual ~Person()= default; //虚函数,下面有
    const string& name ()const{return m name;}
    int age()const{ return m_age;}
    void plusOneYear(){++m_age;}
	void plusOneYear() ++m age; //年龄自增
};
           
           
 //派生类,需指明基类,形式为" class 派生类名:访问限定符 基类名称{};"
class Student:public Person{ //学生类,公有继承Person 私有的和protected的无法访问,
private:
    Course m_course; //课程信息,也是一个类
public:
    Student(const string &name, int age, const Course &c):Person(name,age),m_course(c){}
    Course course(){return m_course;}
};
 

Construction of derived class objects

Student::Student(const string &name, int age,const course &c):
	Person(name,age) /*初始化基类成员*/
    m_course(c)/*初始化自有成员*/
    {}

polymorphism

Polymorphism includes: compile-time polymorphism and run-time polymorphism. Compile-time polymorphism refers to deciding which version of the function to call when the program is compiled, which is realized through function overloading and template instantiation. Runtime polymorphism is belonging to an interface, multiple implementations. **What exactly do you mean? ** That is to say, the following virtual function, declared as a virtual function in the base class is an interface, but has different implementations in different derived classes.

virtual function

A virtual function indicates that the function needs to be implemented differently in different derived classes, so the function in the base class is declared as a virtual function.

Inline functions, static members, and template members cannot be declared virtual. Because the behavior of these members must be determined at compile time, dynamic binding cannot be achieved.

simple input and output

During the input and output process, the program opens a memory buffer for each data stream in memory. During the input operation, the data input from the keyboard is first placed in the buffer of the keyboard. When the Enter key is pressed, the data in the buffer of the keyboard flows to the input buffer of the program to form a cin stream, and then the input operator > > Extract data from the input buffer and save them to the memory associated with the object. When cout<< first outputs data to the console window during the output operation, first send the data to the output buffer in the program for storage, until the buffer executes the refresh operation, all the data in the buffer is sent to the console window for display .

Reasons for output buffer refresh: the buffer is full, the program ends normally, endl is encountered, etc. endl, flush, ends, etc. can force the buffer to be flushed.

cout<<"endl"<<endl; //输出endl和一个换行,然后刷新缓冲区
cout<<"flush"<<flush; //输出flush(无额外字符),然后刷新缓冲区
cout<<"ends"<<ends; //输出ends和一个空字符('\0'),然后刷新缓冲区
//显然他们三个还是有所不同的

Whitespace characters (space, tab, carriage return, etc.)

These characters will be filtered out by the system when they are entered. To get these characters, you can usecin.get()

for(char c;(c=cin.get())!='\n') //只要不遇到'\n',就能继续输入
	cout<<c;
cout<<endl;

getline(cin,s); //该函数可以获取一行字符序列,遇到回车符结束,不包括回车符

formatting control

Formatting control can include data base, precision and width, etc.

//数据进制控制
cout<<showbase<<uppercase; //showbase设置数据输出时显示进制信息,uppercase设置16进制以大写输出并且包含前导字符X
cout<<"default:"<<26<<endl; //默认
cout<<"octal:"<<oct<<26<<endl; //8进制
cout<<"decimal"<<dec<<26<<endl; //10进制
cout<<"hex"<<hex<<26<<endl; //16进制
cout<<noshowbase<<nouppercase<<dec; //输出形式恢复到默认

//上述输出的结果:
default:26
octal:032
decimal:26
hex:0X1A

Floating-point number format control : print precision, representation, whether to print a decimal point for floating-point values ​​without a decimal point

By default, floating point numbers are printed with 6 digits of precision and are printed with rounding rather than truncating.

You can use the setprecision function or the precision function of the IO object to specify the printing precision.

//设置打印精度
double x = 1.2152;
cout.precision(3);  //此处precision接受整型值3,设置精度为3
cout<<"precision:"<<cout.precision()<<",x="<<x<<endl; //此处precision参数为空,返回当前精度
cout<<setprecision(4);
cout<<"precision:"<<cout.precision()<<",x="<<x<<endl;

//输出
precision:3, x=1.22 //四舍五入,不是截断
precision:4, x=1.215

Set the output format , scientific notation, fixed-point decimal, etc.

cout <<"default format:"<<10*exp(1.0)<<endl;  //默认格式
cout <<"scientific:"<<scientific<<10*exp(1.0) <<endl; //科学计数法
cout <<"fixed decimal:"<<fixed<<10*exp(1.0)<<endl; //定点表示
cout <<"default float:"<<defaultfloat <<10*exp(1.0)<<endl; //恢复到默认状态

//输出
default format:27.1828
scientific:2.718282e+01
fixeddecimal:27182818
default float:27.1828

Width control , setwyou can specify the width occupied by the input and output data, setw accepts an int value, if the data width is larger than the set int value, it will be output according to the actual value, if it is smaller, it will be output in the way of right alignment and left filling. setfillYou can specify characters to fill in the blanks.

int i=-10;
double x=1.2152;
cout <<"i:"<<setw(10)<<i<<endl;
cout <<"x:"<<setw(10)<<x<<endl;
cout <<setfill('*')<<"x:"<<setw(10)<<x<<endl;

//输出
i:       -10
x:    1.2152
x:****1.2152

file stream

IO stream objects are used from the keyboard (cin) and the console window (cout). If you want to read data from the disk or write data to the disk, you need a file stream.

ifstream //从指定文件读取数据
ofstream //向指定文件写入数据
fstream //可以读写数据
ifstream in(ifname); //创建输入文件流对象in,提供文件名ifname初始化
ofstream out; //创建输出文件流对象,没有提供文件名
out.open(ofname); //没提供文件名的话,可以使用open函数关联一个文件。
if (out) //最好检测open操作是否成功
out.close(); //记得关闭文件

ios::inOpen the file for reading.
ios::outOpen the file for writing (the default). If the file already exists, all its original contents will be erased; if the file does not exist, a new file will be created.
ios::appOpen the file for writing, and the data of the writer is appended to the end of the file.
ios::ateOpen an existing file and position to the end of the file.
ios::binaryOpen a file in binary mode, if this mode is not specified, it will default to ASCII mode.

Tools and Technology

Namespaces

To avoid naming conflicts, the global scope is split into many subscopes, each of which is a namespace.

namespace Foo {
	//放置任何可以放在全局作用域中的声明
}
//命名空间可以是不连续的,在这个文件中命名一点,在另一个文件中再命名一点也可以
//命名空间也可以嵌套
namespace Wang{
	namespace Li{
		int dosomething(int x,int y);
	}
}

//访问时则需要先访问外面的命名空间
int x =Wang::Li::dosomething(1,2);

inline namespace

Inline namespaces can be accessed directly in the global scope without adding the namespace name

namespace FirstVersion {
	void fun(int);
}
inline namespace SecondVersion {
	void fun (int);
	void fun(double);
}


//调用
FirstVersion::fun(1);  //调用早期版本fun函数
fun(1);  //调用当前版本fun函数 即second
fun(1.0); //调用当前版本中新增的fun函数,即second

global namespace

Those defined in the global scope are in the global namespace, which can be used directly ::to access members of the global namespace

::memeber_name

exception handling

Errors may occur during the running of the program. In order to ensure that no errors occur during the running of large programs, C++ provides an internal exception handling mechanism. Contains three keywords try, catch, throw

throw throws an exception

try detects code that may throw exceptions

catch catches the exception and handles it

After the try detection exception occurs, the system checks the catch clause associated with the try, and if it cannot find it, calls the function in the standard library.

double divide (int a, int b){
	if(b==0)
		throw "Error,division by zero!"; //抛出异常
	return a / b;
}


int a=1,b=0;
try {
	int c=divide(a,b); //异常检测
}
catch(const string &str){ //异常处理
	cerr <<str <<endl;  //cerr为标准错误ostream对象,常用于输出程序错误信息
}
catch(const char *str){  //匹配到这个异常处理,但是为什么匹配到这个不太懂,好像懂了,不对,还是不懂,好像是匹配到throw的异常时char风格的字符串
	cerr <<str<<endl;
}

Standard library exception class , need to include header fileexception

Inheritance relationship of standard exception classes

try{
	throw MyException(); //抛出MyException类型的异常对象
}
catch(exception &ex){
	cerr <<ex.what() <<endl; //捕获
}

Multiple Inheritance and Virtual Inheritance

Multiple inheritance has been mentioned above, that is, a derived class has multiple base classes, which is also normal. For example, a "student" can first inherit the class "person", and he can also inherit the class "child" and have multiple identities . Bats can inherit the Mammal class, and they can also inherit the Flying class.

Multiple inheritance may cause ambiguity problems. The ambiguous problem is known as the death diamond problem.

class Animal{protected: //基类Animal
	int m_age;
public:
	Animal(int n =0):m_age(n){}
	virtual void eat(){}
};
class WingedAnimal:public Animal{ //继承Animal 
public:
	virtual void feedMilk(){}
};
class Mammal: public Animal{ //继承Animal
public:
	virtual void flap(){}
};

class Bat:public WingedAnimal,public WingedAnimal{}; //Bat多重继承
Bat b;
b.eat(); //二义性访问,继承的两个类中都继承了Animal中的eat(),该访问哪一个那

This problem can be solved by virtual inheritance

Virtual inheritance is to declare the base class of a derived class as the base class, so no matter how many times the base class is inherited by others, it only shares the only virtual base class member

class WingedAnimal:virtual public Animal{/*...*/};
class Mammal :virtual public Animal[/* ...*/);

time and date

C++ provides a library chrono for manipulating dates and times. The library provides three clock classes system_clock steady_clock high_resolution_clock

Clock class name real-time
system_clock Real-time clock
(changes with the real-world time adjustment, such as daylight saving time will set the standard time forward by one hour, is there any?)
steady_clock Monotonic clock (does not change with external time adjustments)
high_resolution_clock Real Time Clock (higher accuracy)
using namespace chrono; 
time_t tt = system clock::to_time_t(system_clock::now());
//to_time_t函数将获取的时间点转换成time_t类型
//now获取其数据成员的时间点time_point

cout <<put time(gmtime(&tt),"F")<<endl; 
//gmtime函数将time_t类型函数转换为日历时间
//put_time函数将日历函数转换为时间标准输出流

//输出
2017-12-09 20:15:08
//输出时间间隔
auto start = steady_clock::now();
doSomething();  //执行某种算法
auto end = steady_clock::now();
auto interval = duration_cast <milliseconds > (end - start);
cout <<interval.count () <<endl;

The review of this article is not over, and there is still a small part of the content that has not been written, basically the use of some specific functions.
If you think my writing is good, please give me a free like! If there are errors in the content, please also give me feedback.

Guess you like

Origin blog.csdn.net/qq_45830323/article/details/130161693