C++学习心得

2023-11-19

C++学习心得
一周的C++学习结束了 从C++的简介,各种专业术语的介绍到最后的标准模板库,对于这个c的加强版的语言有了一定的认识理解,但是由于6天时间学完了全部,而且由于疫情在家里上了两天网课,对于一些运用层次还不是很熟悉,学的重点放在了面向对象。对于学到的内容,对重点进行了以下整理,太过简单的或者不重要的仅作了解,不做概述。
C++里的特性及专业术语
(1)类:class
描写现实世界中的事物(包括特征和行为),例如:人,特征:身高、年龄、性别、肤色,行为:吃饭、走路、睡觉等等,它实质是抽象的描述事物,它其实是不存在的。
(2)对象
对象是类的实例化,实实在在存在于现实世界中,特指的概念
(3)继承
相当于是一个新类共享了另一个或多个类的特征和行为,该新类和其他类的关系,称之为继承,例如:儿子 父亲 ----继承
(4)多态
多种形态,一种接口,多种方法,举例:你(8岁) 父亲 -----买票行为-----你买票(半票)—父亲(全票)
(5)封装
c语言里函数就是封装的概念,将数据和数据的操作封装在一起
(6)组合(聚合)
一个类中包含了另一个类的对象,就将类和另一个类的对象的关系,称之为组合关系
(7)静态联编(静态绑定)
一个数据的表达式的类型在编译的时候就已经确定了,称之为静态绑定
(8)动态绑定(动态联编)
一个数据的表达式的类型在运行的时候才确定,称之为动态绑定,典型:多态
(9)特征&属性
在C++特征&属性就是代表变量,如果变量存在于类中,那么称之为成员变量
(10)行为&方法
在C++行为&方法就是代表函数,如果函数存在于类中,那么称之为成员函数
4、C++的程序的编译
跟C编译过程类似

#include <iostream>
#include <cstdio>
//如果想要用到C库里的内容,需要包含C库的头文件,头文件的格式:头文件前面加上c,不需要写后缀.h
//#include 宏----后面紧跟的内容是库文件或者是用户自定义的头文件
//<>-----如果是使用<>符号来包含头文件或者是库文件,那么首先会去标准库里去找头文件
//“”-----如果是使用“”符号来包好头文件或者是库文件,那么它首先是去当前的工程路径去寻找相应的内容
//iostream----iostream是C++提供标准库里的文件,所以没有加后缀.h,如果这里是包含的用户自定义的头文件,那么就需要加上后缀.h
//iostream----原因:cout流对象(它是ostream类的实例化),它是在iostream里实例化
using namespace std;//使用标准库提供的命名空间std,cout/cerr/clog/cin/endl/ends等等它都是属于命名空间std里的内容
int main()//main函数程序执行的开始也是程序执行的结束
{
	printf("你好,世界");
	cout << "hello world" << endl;//打印hello world
	//cout输出流对象,可以用于输出相关信息,它是在头文件iostream里实例化
	//endl结束换行流对象,类似于C里“\r\n”
	//<<----相当于输出仪器
	//“hello world”——向控制台输出的内容
	return 0;//程序的结束标志
}

首先学习的命名空间

//如何进行声明
//关键字--------namespace
namespace 标识符的名称
{
	//代码块或者是语句块
}
//例如:
namespace  zhang
{
	int a;
}
//如何进行访问
//1命名空间的名称  ::需要访问的内容
namespace  zhang
{
	int a;
}
zhang ::a;
//2
namespace  zhang
{
	int a;
}
using  zhang::a;//如果后面用到a,那就表明是命名空间里的a

```//访问格式3:
//关键字:using、namespace
//格式:
//	using namespace 命名空间的名称;//告诉程序在该语句后用到命名空间里定义的变量、函数都是属于命名空间里的内容,相当于是可以访问命名空间里的所有的内容
//例如:
namespace  zhang
{
	int a=50int b=60;
}
using namespace zhang;
//无名(没有名字)的命名空间
//声明格式:
namespace     //表示声明的无名的命名空间
{
	//代码块或者语句块
}
//例如:
namespace
{
	int a;
}

//访问格式:
//符号:域作用符号------::
//格式:
	::需要访问的内容
//例如:
namespace
{
	int a;
}
::a;

注意的是:
(1)命名空间是可以发生嵌套
(2)命名空间可以分为有名的命名空间和无名的命名空间(实际开发中有名的名空间用的比较多)
(3)命名空间是属于全局的一部分,只不过是取了全局的一块区域命了一个名称而已
(4)访问命名空间的时候需要用到域作用符号
然后学习了开辟内存
动态内存
动态内存:程序在运行时才分配的空间,称之为动态内存,例如,在C语言里,可以用malloc和free两个函数,来动态的开辟堆区空间和释放堆区空间,C++提供了new和delete操作符来操作堆区的空间(动态的开辟空间)
new:用于开辟堆区空间,相当于是C里的malloc
delete:用于释放堆区空间,相当于是C里的free
1、开辟变量地址空间
操作符:new
格式:
数据类型(内置的基本的数据类型和自定义的数据类型) * 标识符名称 = new 数据类型 ;//在堆区开辟变量地址空间,用标识符名称来指向堆区空间,堆区空间的大小就是数据类型的大小
例如:

	int   * p  = new int//在堆区开辟int类型大小的空间,并且用p指向该空间
	//或
	int * p ;
	p =new int

释放变量地址空间:

delete p;

开辟变量地址空间的时候同时进行初始化工作:
格式:
数据类型(内置的基本的数据类型和自定义的数据类型) * 标识符名称 = new 数据类型(初始值) ;//在堆区开辟变量地址空间,用标识符名称来指向堆区空间,堆区空间的大小就是数据类型的大小,同时向堆空间里存放初始值
例如:

	int * p = new int50;//开辟堆空间的时候同时对该空间进行初始化工作,初始化为50

释放:

delete p;

开辟数组地址空间(一维数组)
格式:
数据类型(内置的基本的数据类型和自定义的数据类型) * 标识符名称 = new 数据类型 [ 数组的大小 ] ;//在堆区开辟变量地址空间,用标识符名称来指向堆区空间,堆区空间的大小就是数据类型的大小,同时指定数组的大小
例如:

int *arr  = new int[10] ;//在堆区开辟一个具有10个元素的数组空间

释放:

delete [] arr ; 

开辟数组地址空间的时候同时进行初始化工作:
格式:
数据类型(内置的基本的数据类型和自定义的数据类型) * 标识符名称 = new 数据类型 [ 数组的大小 ] {元素值};//在堆区开辟变量地址空间,用标识符名称来指向堆区空间,堆区空间的大小就是数据类型的大小,同时指定数组的大小和初始值
例如:

int *arr = new int[5]{1,2,3,4,5};

释放:

delete [] arr;

然后是最重要的类和对象
类和对象
1、类(class)
描述现实世界中的事物,用C++进行编程的时候,通常是将现实世界中的事物用类来进行描述,类的关键字:class,描述一个人,特征:姓名、年龄、性别、肤色等等,行为:吃饭、睡觉、走路、跑步等等,类是一个泛指(抽象)的概念,不是特指某一个事物,简单的理解为没有实实在在存在于现实世界中(只是你脑海里一个概念)
用类描述事物的格式:
class 类名(标识符名称)
{
//特征—变量
//行为—函数
};
例如:描述一个人,人具备:姓名、年龄、性别、肤色等特征, 具备:吃饭、睡觉、走路、跑步等行为

class People
{
	//特征
	char name[5];//name用于描述姓名
	int age;//age用于描述年龄
	char sex;//sex用于描述性别
	char fuse;//fuse用于描述肤色
	//行为
	void eat()//吃饭
	{

	}
	void sleep()//睡觉
	{

	}
	void walk()//走路
	{

	}
	void run()//跑步
	{

	}
};

2、对象
对象是类的实例化(特指实际的例子),是特指的概念,例如:人(泛指的概念)-----对人进行实例化------张三(特指的概念)
对象的实例化格式:
(1)在栈区实例化--------栈区实例化的对象,不需要用户手动的去释放内存,编译器会自动释放内存
格式:
类名 对象名(标识符名称);
例如:

People  zhangsan;
类似C里的------>int   a;

(2)在堆区实例化-------堆区实例化的对象,需要用户手动的去释放内存,如果不释放,很容易造成内存泄露
格式:
类名 * 对象名(标识符名称)= new 类名;
例如:

People * lisi  = new  People;

(3)如何访问类里面的成员(成员变量+成员函数)
根据在内存里实例化的方式不同,访问也有所不同
a、在栈区实例化
访问成员格式:
对象名(标识符名称). 成员;
例如:

zhangsan.eat();//访问People里的eat方法

b、在堆区实例化
访问成员格式:
对象名(标识符名称)-> 成员;
例如:

lisi->sleep();

类的成员访问修饰符
作用:为了保护类的成员,C++提供了三种类的成员访问修饰符
public(公共)、protected(受保护)、private(私有)
访问修饰符的作用范围:
a、从public:后面开始一直到类体结束(类的后括号之前 })都是属于public属性下的成员
b、如果有两个或者两个以上的成员访问修饰符,从第一个访问修饰符开始到另一个当问修饰符开始都是属于第一个访问修饰符的作用范围
(1)public(公共)
用户把变量或者是函数放置在类的public下,那么这些变量和函数就具有公共的属性

a、在public下的成员对于整个类都是可见的
b、在public下的成员对于类体外也是可见的
c、在public下的成员对于友元函数也是可见的
d、在public下的成员对于派生类(子类)也是可见的
(2)private(私有)
用户把变量或者是函数放置在类的private下,那么这些变量和函数就具有私有属性
a、在private下的成员对于整个类都是可见的
b、在private下的成员对于类的外部是不可见的
c、在private下的成员对于友元函数是可见的
d、在private下的成员对于派生类(子类)是不可见的
(3)protected(受保护)
用户把变量或者是函数放置在类的protected下,那么这些变量和函数就具有保护属性
a、在protected下的成员对于整个类都是可见的
b、在protected下的成员对于类的外部是不可见的
c、在protected下的成员对于友元函数是可见的
d、在protected下的成员对于派生类(子类)是可见的
注:
a、如果在类里面没有写任何的类的成员访问修饰符,那么该类下的成员都是归于private属性下。
b、一般将成员变量放置到private属性下
构造函数
构造函数是一种特殊的函数,它实质还是称为函数,如果用户没有显示的书写构造函数,那么编译器会帮用户自动的生成了一个默认的构造函数,反之,那么编译器就不会自动生成默认的构造函数
作用:就是为了初始化类的成员变量
格式:
类名 (参数列表)
{
//代码块—用于初始化类的成员变量
}
例如:

	People (参数列表)
	{
		
	}

如何去初始化类的成员变量:
(1)直接初始化类的成员变量
例如:

	class Test1
{
public:
	int a;
	Test1()
	{
		a = 10;  //在构造函数里对a进行初始化工作
	}
};

(2)通过初始化列表,对类的成员变量进行初始化
格式:
类名 (参数列表):成员变量1(初始值1),成员变量2(初始值2),…
{
//代码块
}

例如:

class Test1
{
public:
	int age;
	char sex;
	const char* name;
	Test1():age(20),sex('女'),name("张三")
	{
	
	}

};

(3)如何调用构造函数?
a、构造函数的显示调用
格式:
类名 对象名(构造函数参数列表);//这时候就显示的调用类的构造函数
例如:

	People people();

b、构造函数的隐式调用
格式:
类名(构造函数参数列表);//隐式的调用类的构造函数
例如:

People();

》》总结:构造函数与普通函数的区别
不同点:
a、普通函数前有返回值类型,而构造函数前没有返回值
b、普通函数后面没有初始化列表,而构造函数后有初始化列表
c、普通函数调用的时候以显式调用的方式,而构造函数不仅可以显示的调用还可以隐式的调用
相同点:
d、都可以带参数或者是不带参数
e、都可以发生重载

5、析构函数
C++里的析构函数也是一种特殊的函数,它实质也是函数,如果用户没有显示的书写析构函数,那么编译器会自动帮用户生成一个默认的隐藏的析构函数,反之,那么编译器就不会帮用户生成一个析构函数。
作用:释放空间
格式:
~类名 ()
{
//代码块
}
例如:

~Test1()//析构函数
	{
		cout << "这是析构函数" << endl;
	}

(1)如何调用析构函数?
调用时机:
a、当类的对象使用完成之后,会调用一次析构函数
b、当类的指针对象被销毁的时候,会调用一次析构函数
c、当main函数体结束时会调用析构函数

总结:
a、析构函数不能带参数
b、类体里面只允许有一个析构函数(析构函数不能发生重载)
c、析构函数前没有返回值

6、拷贝构造函数
拷贝------copy,如果用户需要用一个对象去初始化另外一个对象的时候,就会用到拷贝构造函数,其实质就是一个构造函数,如果用户没有显示的将拷贝构造函数写出来,那么编译器会自动的生成一个默认的隐藏的拷贝构造函数,反之,编译器就不会帮用户生成一个拷贝构造函数
C:
int a =10;
int b = a;
C++:
用于两个对象(自定义的类型)之间的初始化

	People people1;
	People people2=people2;

拷贝构造函数的格式:
类名(const 类名 & obj)
{
//拷贝构造函数代码块
}
例如:

	People(const People & obj)
	{
		//代码块
	}

(1)浅拷贝
相当于是把一个对象里的成员变量和它的值拷贝了一份儿给另外一个对象。
如果类里面有指针对象并且在堆区开辟了空间,而且在析构函数里对指针对象进行了释放,这时候如果使用浅拷贝,造成堆区空间重复释放,而引起程序崩溃
(2)深拷贝
相当于是把一个对象里的成员变量拷贝了一份儿,但是变量的地址又重新开辟了一个空间,解决的问题就是堆区重复释放造成程序崩溃的问题

注:
如果类里面没有指针对象在堆区开辟空间,那么浅拷贝和深拷贝的作用是一致的

7、this指针
它是一种特殊的指针,它是存在于类的普通成员函数里,C++提供this指针的目的是为了解决二义性问题,并且它是隐式的指针,它是指向当前类。
原型:
成员函数(类名 * const register this)
使用:
this->成员

注意:
静态成员函数中没有this指针&友元函数中没有this指针

8、static
用static声明函数或者是变量为静态的,作用跟C里讲的static作用一样
(1)静态成员函数
格式:

static 返回值类型  函数名(参数列表)
	{
		//函数体
	}

例如:

static void funciton()
	{
		//函数体
	}

静态成员函数的访问格式:
a、通过类的对象访问类的静态成员
格式:
对象名.静态成员;
对象名->静态成员;
b、通过类加上域作用符号来访问类的静态成员
格式:
类名 ::静态成员;

注意:
a、静态成员函数里没有this指针
b、静态成员函数不能访问类的非静态成员
c、类的非静态成员函数可以访问类的静态成员
(2)静态成员变量
格式:
static 数据类型 变量名;

例如:

	static int a;

注:
在类里面不能对类的静态成员变量进行初始化,需要在类外对静态成员变量进行初始化

类外初始化的格式:
数据类型 类名 ::静态成员变量名 = 初始值;
例如:

	int  Test::a  = 70;

9、const
(1)const修饰成员变量
作用跟C类似,有区别的是在类里声明的const成员变量可以不用声明的时候进行初始化,如果没有进行初始化就必须在构造函数的初始化列表中进行初始化的工作
在C里:
const int a; //在声明a的同时必须对它进行初始化
在C++里:
const int a; //可以在声明a的时候不用进行初始化, 那么必须在构造函数初始化列表中进行初始化
(2)const修饰的成员函数
作用:用const修饰的成员函数中不能去更改类的成员变量的值(即const修饰的成员函数里对类的成员变量是一个只读的行为)
格式:
返回值类型 函数名 (参数列表)const
{
//函数体
}
例如:

void function()const
	{
		//函数体
	}

10、友元
目的:给类的外部提供一个访问类的私有成员的接口
关键字:friend,用关键字来声明友元函数和友元类
(1)友元函数
C++提供了友元函数就可以通过友元函数访问到类的私有成员,C++规定是友元函数声明在类里,就表示该函数是该类的友元,那么就可以通过友元函数访问类的私有成员,友元函数的定义放置在类的外部。
声明格式(在类里声明):
friend 返回值类型 函数名(参数列表);//友元函数的声明
定义(在类外定义):
返回值类型 函数名(参数列表)
{
//函数体
}
注意:
a、友元函数虽然声明放置在类的内部,但是它不属于类的成员函数
b、友元函数没有this指针
c、给类的外部提供一个访问类的私有属性成员的接口
(2)友元类
C++提供了友元类就可以通过友元类访问类的私有成员,C++规定是友元类声明在一个其他类里,就表示该友元类是其他类的友元,那么就可以通过友元类访问其他类的私有成员,友元类的定义放置在其他类的外部。
声明格式:
friend class 类名;//声明了一个友元类

定义(在其他类外定义):

class  Test
	{
		//类体
	}

符号:>>(输入符号)

cin:console input;

使用方式:
cin>>基本类型的变量;//从磁盘或者是终端设备获取数据存入到变量里
在C语言里“>>”代表移位运算符,并且该运算符只能用于基本类型的数据的移位操作,在C++里对该运算符进行了重载的工作,该运算符就可以用于其他类型的数据(例如自定义类型的数据),在输入这里,C++对“>>”进行了重载,就表示可以用它来输入数据
在内部重载的格式:
istream & operator>>基本类型的数据;
基本类型的数据:
int、float、double、char、char*、const char *、signed int等等

》注:
cout、cerr、clog、cin、endl、ends、flush等这些不是C++预留的关键字,它们都是流对象(类的实例化),这些对象都是在iostream里实例化(使用的时候需要包含iostream头文件)

什么时候获取输出缓冲区里的值?
a、当程序执行完成时(main结束),会自动刷新输出缓冲区一次
b、当遇到endl(换行对象):endline,会自动刷新输出缓冲区(ends、flush)
c、当输出缓冲区满的时候,会自动刷新输出缓冲区
注:
iostream里提供了一个宏:BUFSIZ,查看缓冲区的大小---------512个字节

五、运算符重载
1、重载、重写、隐藏(覆盖)
(1)重载
一般是发生在函数中,C++也支持运算符重载
解释:
C++里的函数名相同、函数的参数个数、类型、顺序不同,可以发生重载(函数重载)
例如:

	void function()
	{

	}
	void function(int{

	}
	void function(char{

	}
	void function(int , char{

	}
	void function(char , int{

	}

(2)重写
就是将原来函数重新复制一份(函数的返回值、函数名、参数个数、类型、顺序都相同)
例如:

	void funiton()
	{

	}
	void funiton()
	{

	}

(3)隐藏(覆盖)
就是将原来函数(变量)重新复制一份,就将原来的函数(变量)覆盖(隐藏),隐藏一般用的最多的是具有继承关系的两个类中

2、运算符重载
C++里对基本的运算符重新赋予新的运算,例如,以前的加法(+)只能用于两个基本类型的数据进行相加,但是对于非基本类型的数据就不适用。C++对基本的运算符进行重载,重载之后就可以用于非基本类型的数据的运算。
在类中运算符重载实际上是给类定义运算(类的对象能进行基本运算符的运算操作),强制修饰为函数的调用
算术运算符:+、-、、/、%、++、–
位操作运算符:&、|、~、^(位异或)、>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>=、>等等
赋值运算符:=、+=、-=、
=、/=、%=、&=、|=、^=、>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]、指针运算符
注意事项:
A、除成员访问运算符“.”、成员指针运算符“.
”和“->*”、作用域运算符“::”、sizeof运算符和三目运算符“?:”、预处理符号“#”以外,C++中的所有运算符都可 以重载(其中“=”和“&”不必用户重载)
B、重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符
C、运算符重载的实质是函数重载,遵循函数重载的选择原则
D、重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构
E、运算符重载不能改变该运算符用于内部类型对象的含义
F、运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符
G、重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数
H、重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则
I、运算符重载可以通过成员函数的形式,也可是通过友元函数,还可以是非成员函数的形式。
(1)普通成员函数的运算符重载
关键字:operator
声明格式:
返回值类型(可以是基本的数据类型&自定义的数据类型) operator 被重载的运算符 (参数列表);
定义格式:
返回值类型(可以是基本的数据类型&自定义的数据类型) operator 被重载的运算符 (参数列表)
{
//函数体
}

例如:

class Test
{
public:
	int a = 10;
	int b = 20;
	//实现两个对象中a相加并且b相加
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a +obj.a;
		test.b = this->b +obj.b;
		return test;
	}
};

int main()
{
	Test test1;
	Test test2;
	Test test3;
	test3 = test1 + test2;
	cout << "test3.a=" << test3.a << endl;
	cout << "test3.b=" << test3.b << endl;
	return 0;
}	

(2)友元函数的运算符重载
关键字:friend、operator
声明格式:
friend 返回值类型(基本的数据类&自定义的类型) operator 被重载的运算符 (参数列表);
定义:
返回值类型(基本的数据类&自定义的类型) operator 被重载的运算符 (参数列表)
{
//函数体
}
例如:

class Test
{
public:
	int a = 10;
	int b = 20;
	//实现两个对象中a相加并且b相加
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a + obj.a;
		test.b = this->b + obj.b;
		return test;
	}
	friend Test operator -(const Test& obj1, const Test& obj2);
};
Test operator -(const Test& obj1,const Test &obj2)
{
	Test test;
	test.a = obj1.a - obj2.a;
	test.b = obj1.b - obj2.b;
	return test;
}
int main()
{
	Test test1;
	Test test2;
	Test test3;
	Test test4;
	test3 = test1 + test2;
	test4 = test1 - test2;
	cout << "test3.a=" << test3.a << endl;
	cout << "test3.b=" << test3.b << endl;
	cout << "test4.a=" << test4.a << endl;
	cout << "test4.b=" << test4.b << endl;
	return 0;
}

(3)一般函数的成员运算符重载
关键字:operator
声明格式:
函数返回值类型(自定义的类型&基本的数据类型) operator 被重载的运算符 (参数列表)
{
//函数体
}

例如:

class Test
{
public:
	int a = 10;
	int b = 20;
	//实现两个对象中a相加并且b相加
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a + obj.a;
		test.b = this->b + obj.b;
		return test;
	}
	friend Test operator -(const Test& obj1, const Test& obj2);
};
Test operator -(const Test& obj1,const Test &obj2)
{
	Test test;
	test.a = obj1.a - obj2.a;
	test.b = obj1.b - obj2.b;
	return test;
}

Test operator *(const Test& obj1, const Test& obj2)
{
	Test test;
	test.a = obj1.a * obj2.a;
	test.b = obj1.b * obj2.b;
	return test;
}

int main()
{
	Test test1;
	Test test2;
	Test test3;
	Test test4;
	Test test5;
	test3 = test1 + test2;
	test4 = test1 - test2;
	test5 = test1 * test2;
	cout << "test3.a=" << test3.a << endl;
	cout << "test3.b=" << test3.b << endl;
	cout << "test4.a=" << test4.a << endl;
	cout << "test4.b=" << test4.b << endl;
	return 0;
}

六、继承与多态
1、继承
一个类的对象共享了另一个类或其他多个类的成员变量和成员方法(子类还可以自己扩充自己的特征和行为),例如:儿子继承父亲(儿子具备父亲的特征和行为),该类和另一个类或其他类的关系,称之为“继承”,C++继承又分为单继承和多继承。
单继承:
一个类的对象共享了另一个类的成员变量和成员方法
多继承:
一个类的对象共享了其他多个类的成员变量和成员方法
单继承的格式:

class  新的类名(子类)  :  继承的权限   已经存在的类(父类)
	{
		//类体
	};

//新的类我们称之为子类(派生类),已经存在的类我们称之为父类(基类)
//继承的权限:public、private、protected
例如:

class Father
{
public:
	int a = 50;
	void function()
	{
		cout << "这是父类的成员函数" << endl;
	}
};
class  Son : public Father //表示Son类继承自Father
{
public:
	void son_func()
	{
		cout << "a=" << a << endl;
	}
};
int main()
{
	Son zhangsan;
	zhangsan.son_func();
	zhangsan.function();
	return 0;
}

(1)父类和子类的构造和析构函数的调用时机
构造时,先调用父类的构造函数,再调用子类的构造函数,析构时,先调用子类的析构函数,再调用父类的析构函数
(2)如果子类和父类有同样的特征或者是行为?
如果子类和父类有相同的变量或者是函数,那么子类会自动屏蔽(隐藏)父类与子类相同的变量或者是函数,如果子类和父类没有相同的变量或者是函数,那么父类的变量和函数就会继承到子类

2、继承的权限
C++提供了继承的权限:public、private、protected,目的是对继承加以限制
public继承:
(1)父类的public下的成员就全部继承到子类的public属性下
(2)父类的protected下的成员变量是继承到子类的protected属性下,父类的protected下的成员函数没有继承到子类
(3)父类的private下的成员是没有继承到子类的private属性下
protected继承:
(1)父类的public下的成员就全部继承到子类的protected属性下
(2)父类的protected属性下的成员全部继承到子类的protected属性下
(3)父类的private下的成员是没有继承到子类的private属性下
private继承:
(1)父类的public下的成员就全部继承到子类的private属性下
(2)父类的protected属性下的成员全部继承到子类的private属性下
(3)父类的private下的成员是没有继承到子类的private属性下
注:
一般都是选择共有(public)继承
3、多继承
一个新的类继承多个已经存在的类,称为多继承,例如:儿子不仅继承父亲的一些特征和行为,并且儿子继承母亲的特征和行为
声明的格式:
class 新的类(子类或者是派生类):继承的权限1 已经存在的类1(父类1), 继承的权限2 已经存在的类2(父类2),…
{
//类体
}
例如:

class Father1
{
public:
	int a = 60;
protected:
	int b = 70;
private:
	int c = 80;
};

class Father2
{
public:
	int sum = 60;
};

class Son :public Father1,private Father2
{
public:
	void function()
	{
		cout << "b=" <<  b<< endl;
	}
protected:

};

4、多态
按照字面的意思:多种形态(多种方法),C++里一般称为“一种接口,多种方法”,一种接口,指的是父类接口,多种方法表示父类中有方法并且子类中也有相同的方法(方法相同,实现的内容不同)
构成多态性:
(1)两个类具有继承关系
(2)子类中必须重写父类的方法(子类方法中的内容与父类中的方法可以不一样)
(3)父类中的方法必须是虚函数
a、虚函数
虚函数相当于是一个普通的成员函数,只不过在该函数前加了一个关键字“virtual”,那就表明该函数是一个虚函数
声明格式:
virtual 返回值类型 函数名 (参数列表);
定义格式:
virtual 返回值类型 函数名 (参数列表)
{
//函数体
}

例如:

virtual void function()
	{
		//函数体
	}

(1)多态性的调用时机
当父类的指针或者是引用指向子类时
注:
具有继承关系的两个类,是支持父类和子类相关联(父类的指针或者是引用指向子类)
(2)静态绑定
函数的定义和调用在编译阶段就确定了,称之为静态绑定,在C里的函数就是如此,在C++里函数的重载也是如此
在类中:
静态绑定的时候是根据类的指针对象或者是引用的类型来确定函数的调用,而不是通过类的指针或者是引用指向的对象的类型来确定
(3)动态绑定
函数的定义和调用在运行阶段才确定,称之为动态绑定,例子就是:多态中的虚函数
5、抽象类
java里声明抽象类关键字obstract,现实世界中有些事物是不能实例化对象的,我们将这些事物声明为抽象类,例如:鬼、外星人,抽象类只是给子类提供一个基类,在基类里只是对事物的一些描述,而没有具体的实现。
声明格式:
class 类名
{
virtual 返回值类型 函数名(参数列表)=0;
} ;
例如:

class Student
	{
		virtual void funciton() = 0 ;  //该类只是描述事物具备一些特征和行为,但是没有具体实现行为
	}

(1)纯虚函数
在虚函数的声明后面加上“=0”,就表示该虚函数为纯虚函数,它是构成抽象类不可缺少的条件
声明格式:
virtual 返回值类型 函数名(参数列表)=0;
例如:

virtual void funciton() = 0 ;

(2)注意的点
a、抽象类只是给子类提供一个基类
b、抽象类不能实例化为对象
c、抽象类里只是描述了一些行为,但没有具体实现行为(纯虚函数),需要在子类中去重新实现抽象类的行为

6、虚析构函数
虚-----virtual,就是在析构函数前加上一个关键字virtual,那就表明该析构函数为虚析构函数,当基类指针指向子类对象时,如果释放该基类指针,那么就不会释放子类的析构函数,那么这时候需要将基类的析构函数声明为虚析构函数,释放的时候才会调用子类的析构函数
格式:
virtual ~函数名()
{
//函数体
}
例如:

virtual ~Student()
	{
		//函数体
	}

注:
(1)是将父类的析构函数声明虚析构函数
(2)两个类需要有继承关系
(3)基类指针指向子类的对象

8、虚继承
虚继承就是在继承权限前加上关键字virtual,为了解决多重继承(菱形继承)造成的二义性问题
格式:
class 类1 :virtual 继承权限 类2
{
//类体
}
例如:

class Son :virtual public Student
	{
		//类体
	}

七、异常
异常:是C++中一种容错机制,是一个错误处理系统。可以将问题的发现与问题的处理分离。
异常处理:
一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道 怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现)。提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。
C++提供了三个关键字:throw 、try、catch
throw:
抛出异常
格式:
throw 抛出异常的类型的数据;
try:
检测代码是否产生了异常
格式:

try
	{
		//可以产生的异常的代码块
	}

catch:
捕获异常
格式:

catch(异常的类型)
	{
		//处理产生的异常
	}

1、异常使用的注意事项
(1)如果确定异常已经产生,但是用户没有去写捕获该异常的catch,那么程序的外层继续捕获该异常,如果还是没有捕获到,那么最后就调用windows的terminate来终止程序
(2)try模块后面必须紧跟着一个catch块或者是多个catch块
(3)产生的异常首先是匹配try后紧跟着的第一个catch块,如果没有匹配,继续往下一个catch进行匹配,如果还是没有捕获到,那么最后就调用windows的terminate来终止程序
(4)catch(…)它是用于捕获所有异常
(5)try…catch块可以发生嵌套

2、标准的异常
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 参数超出有效范围
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。
3、自定义异常类
需要继承C++提供的所有异常的基类exception,可以在自定义的异常类重写what()方法,用于查看产生的异常的类型
格式:
class 自定义的异常 :public exception
{
//自定义异常类体
}
例如:

#include"iostream"
using namespace std;
//自定义异常类---用于提示字符串的错误
class MyException :public exception
{
public:
	const char* str;
	MyException(const char* obj)
	{
		str = obj;
	}
	const char* what()
	{
		return str;
	}
};
int function(int a, int b)
{
	if (b == 0)
	{
		throw out_of_range("长度不能超出");//标准的异常
//		throw MyException("用户输入的分母不能为0");//自定义的异常
	}
	else
	{
		return a / b;
	}
}
int main()
{
	try
	{
		function(10, 0);
	}
	catch (int)
	{

	}
	catch (out_of_range& e)
	{
		cout << "产生的异常:" << e.what() << endl;
	}
	catch (MyException& e)
	{
		cout << "产生的异常:" << e.what() << endl;
	}
	return 0;
}

八、模板
模板就是提供一个可参考的对象,例如:字帖(模板),在程序中,例如:void funciton(int a , int b);//对参数的类型不必写死,泛型编程:
泛型:数据的类型是广泛的----泛型
1、函数模板
声明格式:
关键字:template
template <模板形参1,模板形参2,…> 函数的返回值 函数名(函数形参列表);
定义格式:
template <模板形参1,模板形参2,…> 函数的返回值 函数名(函数形参列表)
{
//函数体
}
注意:
(1)模板的形参是用关键字typename/class
格式:
typename 标识符名称

class 标识符名称
例如:
typename T1; //表示T1是函数模板的模板形参,可以用T1来代替内置的基本的数据类型(int\char\string等等)
(2)模板的模板形参要与函数形参一一对应
注意:如果传递多个函数形参的类型是一样的,那么模板形参可以只写一个
(3)模板形参称之为非类型形参(不是基本的内置的类型)
(4)<模板形参1,模板形参2,…>,模板形参不是函数形参
(5)模板函数可以发生重载
(6)模板函数的返回值可以是模板形参类型

(2)类模板
声明格式:
template <模板形参> class 类名
{
//类体
};
例如:

template <class T1>class Test
{
public:
	T1 a;
	Test(T1 b)
	{
		a = b;
		cout << "a=" << a << endl;
	}

};

模板类的实例化:
类名<数据类型>对象名(构造函数的参数列表);
例如:

Test<int>test(10); //表示模板形参的类型为int类型

九、智能指针
智能指针就是管理裸指针开辟的堆空间,换句话说就是将裸指针开辟的堆空间的管理权交给智能指针管理。智能:自动释放管理的堆空间,智能指针其实质是C++提供的一个类,具备裸指针的特性(->, .,*),C++提供了4种智能指针:auto_ptr(自动管理)、shared_ptr(共享指针)、weak_ptr(弱指针)、unique_ptr(唯一指针)
注:
auto_ptr已经被弃用(C++11)
(1)shared_ptr(共享指针)
shared_ptr智能指针
shared_ptr 是C++11提供的一种智能指针类,它足够智能,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。
它遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制来实现这一点。
1、每个 shared_ptr 对象在内部指向两个内存位置:
(1)指向对象的指针。
(2)用于控制引用计数数据的指针。
2、共享所有权如何在参考计数的帮助下工作:
(1)当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
(2)当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它自动调用delete函数删除该内存。
3、引用计数器
shared_ptr 使用经典的 “引用计数” 的方法来管理对象资源。引用计数指的是,所有管理同一个裸指针( raw pointer )的 shared_ptr,都共享一个引用计数器,每当一个 shared_ptr 被赋值(或拷贝构造)给其它 shared_ptr 时,这个共享的引用计数器就加1,当一个 shared_ptr 析构或者被用于管理其它裸指针时,这个引用计数器就减1,如果此时发现引用计数器为0,那么说明它是管理这个指针的最后一个 shared_ptr 了,于是我们释放指针指向的资源,即在引用计数归零时,这个内部类型指针与 shared_ptr 管理的资源一起被释放。
4、shared_ptr成员函数:
(1)use_count() 返回引用计数的个数(即绑定的指针个数)
(2)unique() 返回是否是独占所有权( use_count 为 1)
(3)swap(shared_ptr对象) 交换两个 shared_ptr 对象(即交换所拥有的对象)
(4)reset() 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
(5)get() 返回内部对象(指针)的地址, 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的
5、问题
共享指针循环引用的时候会造成资源得不到释放
(2)weak_ptr(弱指针)
解决共享指针循环引用的时候会造成资源得不到释放的问题,协助共享指针工作
弱指针:与共享指针一起使用,作为共享指针的备份,弱指针的存在,不影响共享指针的释放。
弱指针中的方法:
lock(); 可以将弱指针转换为 共享指针使用,若其指向的空间已经释放,将返回NULL指针
expired(); 检查弱指针指向的空间存在否,若被释放 返回假,存在返回真;
weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作(例如解决shared_ptr因循环引用不能释放资源的问题)。
使用weak_ptr 来打破循环引用,它与一个 shared_ptr 绑定,但却不参与引用计数的计算,不论是否有 weak_ptr 指向,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。weak_ptr 像是 shared_ptr 的一个助手。同时,在需要时,它还能摇身一变,生成一个与它绑定的 shared_ptr 共享引用计数的新 shared_ptr。
总而言之,weak_ptr 的作用就是:在需要时变出一个 shared_ptr,在其他时候不干扰 shared_ptr 的引用计数。
(1)用shared_ptr进行初始化
(2)成员函数expired()用于判断shared_ptr是否被释放

#include "iostream"
using namespace std;

class Son;
class Father
{
public:
	~Father()
	{
		cout << "Father释放资源" << endl;
	}
	weak_ptr<Son>son;//弱指针
};
class Son
{
public:
	~Son()
	{
		cout << "Son释放资源" << endl;
	}
	weak_ptr<Father>father;//弱指针
};
int main()
{
	Father* father2 = new Father();
	Son* son2 = new Son();
	shared_ptr<Father>father1(father2);
	shared_ptr<Son>son1(son2);
	father2->son = son1;
	son2->father = father1;
	cout << "与Father绑定的共享指针的个数:" << father1.use_count() << endl;
	cout << "与Son绑定的共享指针的个数:" << son1.use_count() << endl;
	return 0;
}

(3)unique_ptr(唯一指针)
唯一指针:与共享指针类似,是一种受限制的共享指针,绑定一个指针指向对象。
unique_ptr 是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享《被管理对象指针》所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
unique_ptr具有->和*运算重载符,因此它可以像普通指针一样使用。
格式:
unique_ptr<类型>对象名(指针对象)

十、标准模板库STL(standard template)
C++标准模板库是一种泛型编程,将算法抽象出来,独立于类型编程。
C++标准库提供等容器主要有
数组,链表,队列等等,
可以实现 增删改查、排序等操作

C++标准模板库的六大构成组件
泛型容器(containers)
特殊的数据结构,实现了数组、链表、队列、等等,实质是模板类
迭代器(iterators)
一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载
算法(algorithms)
读写容器对象的逻辑算法:排序、遍历、查找、等等,实质是模板函数
空间配置器(allocator)
容器的空间配置管理的模板类
配接器(adapters)
用来修饰容器、仿函数、迭代器接口
仿函数(functors)
类似函数,通过重载()运算符来模拟函数行为的类
组件间的关系
container(容器) 通过 allocator(配置器) 取得数据储存空间,
algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容,
functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化,
adapter(配接器) 可以修饰或套接 functor(仿函数)。
这些组件最终实现一些常见的数据结构的快速使用。
1、vector(向量)
vector相当于一个动态数组
数组方式连续存储,可以使用[] 符号进行随机访问,
#include “vector”
(1)初始化vector对象的方式:
vectorv1 ; //默认的初始化方式,内容为空
vector v2(v1) ;//v2是v1的一个副本
vectorv3(n ,i) ;//v3中包含了n个数值为i的元素
vectorv4(n) ;//v4包含了n个元素,每个元素的值为0
(2)vector常用函数
empty():判断向量是否为空,为空返回真,否则为假
begin():返回向量(数组)的首元素地址
end(): 返回向量(数组)的末元素的下一个元素的地址
clear():清空向量
front():返回得到向量的第一个元素的数据
back():返回得到向量的最后一个元素的数据
size():返回得到向量中元素的个数
push_back(数据):将数据插入到向量的尾部
pop_back():删除向量尾部的数据
2、list(双向链表)
链表相对于vector向量来说的优点在于:
(a)动态的分配内存,当需要添加数据的时候不会像vector那样,先将现有的内存空间释放,在次分配更大的空间,这样的话效率就比较低了。
(b)支持内部插入、头部插入和尾部插入
缺点:不能随机访问,不支持[]方式和vector.at()、占用的内存会多于vector(非有效数据占用的内存空间)
#include “list”
(1)初始化list对象的方式
list L0; //空链表
list L1(3); //建一个含三个默认值是0的元素的链表
list L2(5,2); //建一个含五个元素的链表,值都是2
list L3(L2); //L3是L2的副本
list L4(L1.begin(),L1.end()); //c5含c1一个区域的元素[begin, end]。
(2)list常用函数
begin():返回list容器的第一个元素的地址
end():返回list容器的最后一个元素之后的地址
rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)
rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)
front():返回链表中第一个数据值
back():返回链表中最后一个数据值
empty():判断链表是否为空
size():返回链表容器的元素个数
clear():清除容器中所有元素
insert(pos,num):将数据num插入到pos位置处(pos是一个地址)
insert(pos,n,num):在pos位置处插入n个元素num
erase(pos):删除pos位置处的元素
push_back(num):在链表尾部插入数据num
pop_back():删除链表尾部的元素
push_front(num):在链表头部插入数据num
pop_front():删除链表头部的元素
sort():将链表排序,默认升序
3、deque(队列)
合并了 vector 和 list的特点,
优点
随机访问方便,即支持[]操作符和vector.at(n)
在内部方便的进行插入和删除操作
可在两端进行push、pop
缺点占用内存多
使用区别
如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
如果你需要大量的插入和删除,而不关心随机存取,则应使用list
如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++学习心得 的相关文章

随机推荐

  • (Ext基础篇) 表单与输入控件

    FormPanel和BasicForm详解 我们制作一个Ext form FormPanel 然后在里面设置field 顾名思议 FormPanel是Ext Panel的一个子类 可以对其执行各种Panel操作 实际上 表单的功能是在Ext
  • vue滚动插件:vue-seamless-scroll

    地址 使用 vue seamless scroll 使用方法 1 下包 yarn add vue seamless scroll 2 局部导入 import vueSeamlessScroll from vue seamless scrol
  • 搜索指定站点和指定文档类型

    对于像我这个不生产代码 只是代码搬运工的程序员来说 搜索引擎是必不可少的 遇到问题 搜索一下 基本都能找到答案 但有时 搜索出来的答案千篇一律 而且垃圾网站一波波 这时就要指定搜索某些 网站 如csdn 1 指定站点 在搜索框里面输入 关键
  • HTTP状态码总结

    为了更好地了解各个状态码代表的意思 做了一个总结 在实际的工作中 主要涉及到的还是200 404 500等 1 HTTP状态码分5大类 状态码 类别 100 199 信息性状态码 200 299 成功状态码 300 399 重定向状态码 4
  • 如何在 wxPython 中创建多个工具栏

    在GUI编程领域 wxPython已经成为一个功能强大且通用的库 使开发人员能够轻松制作令人惊叹的图形用户界面 在众多基本组件中 工具栏在为用户提供对各种功能的快速访问方面发挥着至关重要的作用 在本教程中 我们将深入探讨使用 wxPytho
  • LCD笔记(4)分析内核自带的LCD驱动程序

    1 驱动程序框架 Linux驱动程序 驱动程序框架 硬件编程 在前面已经基于QEMU编写了LCD驱动程序 对LCD驱动程序的框架已经分析清楚 核心就是 分配fb info 设置fb info 注册fb info 硬件相关的设置 1 1 入口
  • 使用AS自带工具转换Java文件为.kt问题总结

    android studio Java项目转kotlin问题总结 起因 崩溃问题 部分类Java 和kotlin api存在差异导致编译时报错 起因 忙里偷闲想学一下kotlin 于是在看了一些kotlin基础以后 把项目拉了个分支 想将J
  • pytorch下import numpy失败_深度学习之Pytorch基础教程!

    关注后 星标 Datawhale 每日干货 每月组队学习 不错过 Datawhale干货 作者 李祖贤 Datawhale高校群成员 深圳大学 随着深度学习的发展 深度学习框架开始大量的出现 尤其是近两年 Google Facebook M
  • lscpu命令详解

    基础命令学习目录首页 一 lscpu输出 使用lscpu查看的结果如下图 这里会显示很多信息 如下 使用lscpu p会详细的numa信息 如下 root localhost lscpu p The following is the par
  • 【Mongodb教程 第五课 】MongoDB 删除集合

    drop 方法 MongoDB 的 db collection drop 是用来从数据库中删除一个集合 语法 drop 命令的基本语法如下 db COLLECTION NAME drop 示例 首先 检查可用的集合在数据库 mydb gt
  • 简单混合运算计算器

    实现一个能够进行简单混合运算的计算器 要求对混合运算的表达式进行先乘除后加减运算 其实现的效果如下图所示 小练习 GUI窗体 面向对象思路 代码 Expression类 package 计算器 import java util Linked
  • 使用R语言实现卷积神经网络cnn进行图像识别

    目录 1 卷积神经网络 CNN 简介 2 安装和加载必要的R包 3 加载和处理数据 4 构建CNN模型
  • 攻防 & 渗透 & Kali笔记(持续更新)

    0x00 写在前面 本来是记录kali用法的一篇文章 后来就慢慢变成了记录攻防 渗透测试 Kali用法的文章了 本来信息安全就涉及到方方面面就是个大杂烩 0x01 John the Ripper john爆破需要一个shadow文件 推荐使
  • Python-import导入上级目录文件

    假设有如下目录结构 dir0 file1 py file2 py dir3 file3 py dir4 file4 py dir0文件夹下有file1 py file2 py两个文件和dir3 dir4两个子文件夹 dir3中有file3
  • IDEA太强悍了!java初级工程师工资多少

    所以 我认为在你选择之前不妨好好想想什么是Java 你适不适合从事这份工作 Java开发是近20多年来最热门的编程语言 就业市场确实比较大 入门的难度也比C和C 要低 结合各方面来说 你选择Java是一定没有问题的 接下来就要好好想想自己适
  • 经典网络ResNet介绍

    经典网络ResNet Residual Networks 由Kaiming He等人于2015年提出 论文名为 Deep Residual Learning for Image Recognition 论文见 https arxiv org
  • 【githubshare】30 天精通 Git 版本管理,主要介绍 Git 的一些常用操作,以及日常工作中实际应用场景讲解

    GitHub 上一份开源的 Git 教程 30 天精通 Git 版本管理 主要介绍 Git 的一些常用操作 以及日常工作中实际应用场景讲解 GitHub github com doggy8088 Learn Git in 30 days 外
  • openssh升级

    openssh下载链接 https ftp openbsd org pub OpenBSD OpenSSH portable 查看ssh版本 ssh V OpenSSH 9 0p1 OpenSSL 1 0 2k fips 26 Jan 20
  • 列表标签使用

    列表标签分为有序列表和无序列表 什么时候使用列表标签 列表也是用来布局使用 它的自由度相对表格来说要大一点 但是它也是可以整齐排列 1 有序列表使用 ol li 苹果 li li 菠萝 li li 香蕉 li ol 2 无序列表 ul li
  • C++学习心得

    C 学习心得 一周的C 学习结束了 从C 的简介 各种专业术语的介绍到最后的标准模板库 对于这个c的加强版的语言有了一定的认识理解 但是由于6天时间学完了全部 而且由于疫情在家里上了两天网课 对于一些运用层次还不是很熟悉 学的重点放在了面向