《C++程序设计》学习
C语言与C++之间的区别
一、C++基于过程的程序设计
1、简单程序实例
example 1:
main函数必须声明为int型,因为操作系统要求执行一个程序后必须向操作系统返回一个数值:如果程序正常执行,则向操作系统返回数值0;否则返回数值-1;
函数体是由”{}”括起来的;
cout是C++系统定义的对象名,称为输出流对象;cin为输入流对象(输入时两个数据间用一个或多个空格间隔,不能以逗号或其他符号间隔);
“#include < iostream >” 是C++的一个预处理指令,是一个包含指令,作用是把文件”iostream”中的内容包含到该命令所在的程序文件中,代替该指令;
“using namespace std“的意思是使用命名空间”std”,C++标准库中的类和函数是在命名空间std中声明的;
“//”代表从它开始到本行末尾之间的全部内容都作为注释,同时对于多行注释也可以用”/ …… /”来表示;
程序第5行是对max函数的声明,声明过后max函数可以写在main函数后面;
2、C++程序的实现过程
每一个程序单位由以下三部分组成:预处理指令、全局声明、函数
3、变量
3.1 变量基础
变量属于标识符。标识符指用来标识变量、函数、数组、类型等实体名字的有效字符序列;
变量名代表内存中的一个存储单元。当程序从变量中取值时,实际是通过变量名找到相应的内存单元,从其中读取数据;
变量具有两种属性:作用域(全局变量或局部变量)、存储期(静态存储期或动态存储期)
变量的命名规则:
只能由字母、数字、下划线3种字符组成;
第一个字符必须为字母或者下划线;
常变量又称为只读变量:
相当于在此变量的基础上加上一个限定:存储单元中的值不允许变化;
区别使用#define指令定义的符号常量和用const定义的常变量:
3.2 局部变量与全局变量
局部变量
在一个函数内部定义的变量,它只在本函数或复合语句范围内有效
形参也是局部变量
在函数原型声明中出现的参数名,只在原型声明中的括号范围内有效
全局变量
在函数外定义的变量是外部变量,称为全局变量,其有效范围为从定义变量的位置开始到本源文件结束
如果在一个函数中改变了全局变量的值,就能影响到其他函数,使其他函数中引用的同名变量也同时改变
建议不在必要时不要使用全局变量(占用存储单元、函数通用性降低)
3.3 变量的存储类别
静态存储方式
在程序运行期间,系统对变量分配固定的存储空间
静态存储区中存放全局变量
动态存储方式
在程序运行期间,系统对变量动态地分配存储空间
动态存储区中存放:函数形式参数、函数中定义的变量(未加static的局部变量)、函数调用时的现场保护和返回地址等
变量的存储类别
指数据在内存中的存储方式
包括自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)
自动变量:函数中的局部变量,如果不加static声明,编译系统动态分配存储空间
静态局部变量:函数中的局部变量的值在调用结束后不消失而保留原值,即其占用的存储单元不释放,下次调用该函数时,该变量保留上一次调用结束时的值。
虽然静态局部变量在函数调用后仍然存在,但其他函数不能引用它,在其他函数中不可见
静态局部变量是在编译时赋初值的(默认为0或空字符),而自动变量赋初值是在函数调用时进行
寄存器变量:需要时直接从寄存器中取出参与运算,不必到内存中存取
全局变量或外部变量:在函数外部定义的,作用域为从函数定义处开始到本程序文件末尾
extern只是对一个已定义的外部变量做声明,以拓展作用域
当使用static在定义外部变量时进行声明,则此外部变量只限于本文件引用,而不能被其他文件引用(静态外部变量)
3.4 变量的声明与定义
函数的声明是函数的原型,函数的定义时函数功能的确立
声明分为定义性声明(需要建立存储空间,eg:int a;)和引用性声明(不需要建立存储空间,eg:extern int a;)
广义来说声明包括定义,不过为了叙述方便,把建立存储空间的声明称为定义,而把不需要建立存储空间的声明称为声明
4、运算符
运算符种类
算数运算符:+、-、*、/、%、++、–
关系运算符:>、<、==、>=、<=、!=
逻辑运算符:&&、||、!
位运算符:<<(按位左移)、>>(按位右移)、&、|、^(按位异或)、~(按位取反)
赋值运算符:=及其拓展赋值运算符
条件运算符:?:
逗号运算符:,
指针运算符:*
引用运算符和地址运算符:&
求字节数运算符:sizeof
强制类型转换运算符
成员运算符:.
指向成员的运算符:->
下标运算符:[]
其他:函数调用运算符
运算符的优先级:!>算数运算符(带有”<”或”>”号的优先级大于”==”和”!=”)>关系运算符>&&和||>赋值运算符
进行运算时不同类型的数据需要转为同一类型,转换规则为如下所示。其中横向向左的箭头表示必定转换;纵向的箭头表示当运算对象为不同类型时的转换方向, 箭头的方向只表示数据类型级别的高低,由低向高转换。
自增和自减运算符:++i(在使用i之前,先使i的值加1)、i++(在使用i之后,i的值加1)、–i(在使用i之前,先使i的值减1)、(在使用i之后,i的值减1)
赋值运算符:如果赋值运算符两侧类型不一致,但都是数值型或者字符型时,在赋值时自动进行类型转换
double赋值给float时,要注意数值范围不能溢出
字符型赋值给整形时,将字符的ASCII码赋给整形
将int,short或long型数据赋给一个char型变量,只将其低8位原封不动地送到char型变量(发生截断)
不同类型整型数据间的赋值归根到底就是一条:按存储单元中的存储形式直接传送
条件运算符(唯一一个三目运算符):
eg:max = (a>b)? a: b;
5 程序结构
5.1 选择结构
if()…else if()…else
多分支选择结构:switch() {case x: …;case y: …;default :…}
各个case和default出现的次序不影响执行结果
在执行switch语句时,根据switch表达式的值找到与之匹配的case子句,就从此case子句开始执行下去,不再进行判断,所以此时需要”break“语句来达到跳出的目的
多个case可以共用一组执行语句
5.2 循环结构
while(表达式)语句
do 表达式 while(表达式)(do-while语句)
for(表达式1;表达式2;表达式3)语句
可以只用”break“或”continue”跳出循环或跳出本次循环
6、函数
6.1 函数的分类
用户使用的角度:
系统函数,即库函数
用户自己定义的函数
函数形式:
无参函数
有参函数
6.2 函数的定义
6.3 函数的参数
6.4 函数的调用
调用的方式 函数语句函数表达式
函数参数
函数调用时注意,当实参列表包括多个实参时,对实参的求值顺序并不是确定的。许多C++系统是按照从右至左的顺序求值。比如,若变量i的值为3时:
6.4 函数的声明与函数原型
函数声明是指函数尚未定义的情况下,事先将函数的有关信息通知编译系统,以便编译可以正常进行。
函数声明中可以不写形参名,只写形参类型,这种声明成为函数原型
float add(float, float);//函数原型
/*函数类型 函数名(参数类型1, 参数类型2…);
函数类型 函数名(参数类型1 参数名1, 参数类型2 参数名2…);
6.5 内置函数(内联函数)
编译时将函数代码嵌套到主调函数中,而不是将流程转出去
可以在声明函数和定义函数同时写inline;也可以只在函数声明时加inline,而定义时不加
内联函数中不能包括复杂的控制语句,如循环和switch语句
6.6 函数的重载
C++允许同一个函数名定义多个函数,而这些函数的参数个数和参数类型可以不相同
不允许只有函数的类型不同而参数的个数和类型相同,如:
6.7 函数模板
- 建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表
- 定义函数模板的一般形式:
只适用于函数体相同、函数参数个数相同而类型不同的情况
6.8 有默认参数的函数
指定默认值的参数必须防止形参列表的最右端
如果函数的定义在函数调用之前,则应在函数定义中给出默认值;如果函数的定义在函数调用之后,则在函数调用之前需要有函数声明,此时必须在函数声明中给出默认值,在函数定义时可以不给默认值
一个函数不能既作为重载函数,又作为有默认参数的函数
6.9 内部函数和外部函数
内部函数:只能被本文件中其他函数调用
static 类型标识符 函数名(形参表)
外部函数:可以供其他文件调用
extern int func(int a, int b)//省略extern,默认为外部函数
6.10 函数参数的传递
将变量名作为实参和形参:形参是变量的值,传递是单向的
传递变量的地址:形参是实参变量的指针变量(地址),实参是一个变量的地址
形参为实参的引用:以引用作为形参,使形参名作为实参的引用
7、数组
7.1数组的定义与引用
类型名 数组名[常量表达式]//一维数组的定义
数组名[下标]//一维数组的引用
类型名 数组名[常量表达式][常量表达式]//二维数组的定义
数组名[下标][下标]//二维数组的引用
二维数组元素排列顺序为:按行存放,即在内存中先顺序存放第1行元素,再存放第2行元素
多维数组元素在内存中的排列顺序:第一维的下标变化最慢,最右边的下标变化最快
7.2用数组做函数参数
数组元素可以做函数参数,用法与变量相同,传递数组元素的值;数组名也可以做实参和形参,传递的是数组的起始地址,C++实际上把形参数组名作为一个指针变量来处处理
/*…*/
void select_sort(int array[], int n);//函数
int a[10];
select_sort(a,10);//调用函数
/*…*/
void select_sort(int array[], int n)//函数定义
{
/*…*/
}
7.3 字符数组
7.3.1 字符数组基础
字符串结束标志:遇到字符\’\\0\’表示字符串结束。对一个字符串常量,系统自动在所有字符后加\’\\0\’作为结束符。程序中检测字符串长度也依靠字符串结束标志
如果输入字符串长于字符数组的长度,此时系统并不报错,而是将多余的字符元素顺序地存放到所定义字符数组后面的几个字节中,有可能破坏其他数据,甚至出现无法估计的后果
7.3.2 字符串处理函数
7.3.3 字符串类
- 将C++标准库中的string头文件包含进来
8、指针
- 一个变量的地址称为该变量的指针
- 专门存放地址(即指针)的变量称为指针变量
- C++中“*”表示指向,比如i_pointer是一个指针变量,则*i_pointer表示i_pointer指向的变量
8.1 指针变量的定义与引用
8.2 指针与函数
8.2.1 指针做函数参数
8.2.2 指向函数的指针变量
8.2.3 返回指针值的函数( )
8.3 指针与数组
8.4 指针与字符串
- 访问字符串:
- 用字符数组存放一个字符串
- 用字符串变量存放字符串
- 用字符指针指向一个字符串
8.5 const指针和void指针
- 指向常量的指针变量:不允许通过指针变量改变它指向的对象的值,但是指针变量p的值(即p的指向)是可以改变的
void指针:指向空类型或不指向确定类型的数据,其为过渡型的
9、引用
相当于给一个变量起别名
在数据类型后面出现的&是引用声明符,在其他场合出现的都是地址符(出现在声明中的&是引用声明符,其他情况是地址运算符)
声明一个引用时,必须同时使之初始化,声明后不能再使之作为另一变量的引用
不能建立引用数组,不能建立引用的引用,不能建立引用的指针
引用机制主要是把它作为函数参数,以扩充函数传递数据的功能(传址方式)
10、用户自定义数据类型
结构体类型(structure)、共用体类型(union)、枚举类型(enumeration)、类类型(class)
10.1 结构体(struct)
结构体中在一个组合项中包含若干个类型不同(也可以相同)的数据项
10.1.1 结构体的声明、定义、初始化与引用
10.1.2 指向结构体变量的指针
10.1.3 结构体类型数据作为函数参数
- 用结构体变量名作参数
- 用指向结构体的指针作参数,将结构体变量的地址传给形参
- 用结构体变量的引用作函数参数,成为实参的别名
10.1.4 new和delete运算符进行动态分配撤销存储空间
10.2 枚举类型(enum)
- 一个变量只能有集中可能的值,可以定义为枚举类型
- 枚举类型按常量处理,成为枚举常量,编译系统按定义时的顺序对它们赋值为0,1,2,3…
10.3 声明新类型(typedef)
- 使用typedef声明一个新的类型名类代替已有的类型名
- 也可以对一个结构体类型声明一个新的名字
最后,如果你想学C/C++可以私信小编“01”获取素材资料以及开发工具和听课权限哦!
一文彻底搞懂设计模式(含C++实例代码)
尽管设计模式是针对面向对象语言提出的,但貌似市面上大多数都是基于java给出的例子,C++的例子极少,本文以C++的例子来谈一下设计模式。
设计模式是主要针对面向对象语言提出的一种设计思想,主要是提高代码可复用性,抵御变化,尽量将变化所带来的影响降到最低。
- 封装:隐藏内部实现
- 继承:复用现有的代码
- 多态:改写对象的行为
- 依赖倒置原则:针对接口编程,依赖于抽象而不依赖于具体,抽象(稳定)不应依赖于实现细节(变化),实现细节应该依赖于抽象,因为稳定态如果依赖于变化态则会变成不稳定态。
- 开放封闭原则:对扩展开放,对修改关闭,业务需求是不断变化的,当程序需要扩展的时候,不要去修改原来的代码,而要灵活使用抽象和继承,增加程序的扩展性,使易于维护和升级,类、模块、函数等都是可以扩展的,但是不可修改。
- 单一职责原则:一个类只做一件事,一个类应该仅有一个引起它变化的原因,并且变化的方向隐含着类的责任。
- 里氏替换原则:子类必须能够替换父类,任何引用基类的地方必须能透明的使用其子类的对象,开放关闭原则的具体实现手段之一。
- 接口隔离原则:接口最小化且完备,尽量少public来减少对外交互,只把外部需要的方法暴露出来。
- 最少知道原则:一个实体应该尽可能少的与其他实体发生相互作用。
- 将变化的点进行封装,做好分界,保持一侧变化,一侧稳定,调用侧永远稳定,被调用测内部可以变化。
- 优先使用组合而非继承,继承为白箱操作,而组合为黑箱,继承某种程度上破坏了封装性,而且父类与子类之间耦合度比较高。
- 针对接口编程,而非针对实现编程,强调接口标准化。
小总结: 没有一步到位的设计模式,刚开始编程时不要把太多精力放到设计模式上,需求总是变化的,刚开始着重于实现,一般敏捷开发后为了应对变化重构再决定采取合适的设计模式。
父类定义算法的骨架,而将一些步骤延迟到子类去实现,使得子类可以复用骨架,并附加特性,以开发框架举例,框架开发人员把框架调用流程定好,而将某些具体的步骤作为虚函数留给子类去重写,话不多说,上代码。
代码很简单,体现的是思想,游戏包含三个步骤,初始化游戏,开始游戏,停止游戏,初始化游戏和停止游戏步骤比较统一,由父类Game定义好,而开始游戏是第二个步骤,可以有打篮球和踢足球,将来也可以有羽毛球,乒乓球等等,每增加一项运动,都可以从Game父类中继承后重写开始游戏这个函数,达到不同的功能,符合模板方法的特性,即如何在确定稳定结构前提下,应对子步骤需求的变化。
定义一系列的算法,将它们一个个封装,使得他们可以相互替换,一般为了解决多个if-else带来的复杂性,在多种算法相似的情况下,通过策略模式可减少if-else带来的复杂性和难以维护性,一般在项目中发现多个if-else并且预感将来还会在此增加if-else分支,那基本上就需要使用策略模式。先举一个不使用策略模式的例子,拿计算来说,下面代码定义了加法操作和减法操作,以后如果需要增加乘法除法等计算,那就需要在枚举里添加新类型,并且增加if-else分支,这违反了开放关闭原则。
下例为使用策略模式,定义一个基类Calculation,包含虚函数operation()。
每增加一种运算,就增加一个继承基类的子类,重写operation()函数。
是不是方便了很多,将来如果有乘法除法和其它运算规则,只需要再加一个继承基类的子类即可。
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)
定义对象间的一对多关系,当一个对象状态发生改变的时候,其它依赖于它的对象都会得到广播通知并进行自定义动作,通过面向对象技术的多态技术,可以降低这种依赖关系,降低耦合度,上代码.
通过观察者模式可以灵活的控制依赖的对象,动态的增加和删除需要得到通知的对象。
动态的给一个对象添加一些额外的职责,扩展一个类的功能,就增加功能来说,使用装饰器模式比单纯的继承子类更加灵活,不一定非狂使用继承方式。举个例子,有游戏这个大类,扩展这个类的功能,有打篮球,踢足球,玩lol,玩卡丁车,可以分别定义继承游戏基类的四个子类,但是如果想组合这几个功能呢,一个对象既会打篮球又会玩卡丁车,既会打篮球又会玩lol,再定义几个类继承游戏基类显然不是好的做法,装饰器模式可以很好的解决这个问题,上代码: 首先定义一个Game基类
定义卡丁车子类和lol子类
篮球和足球子类也可以像上面一样继承定义,但是如果都像上面一样继承的话不能实现动态扩展功能的需求,所以先定义一个装饰类,之后定义继承这个装饰类的篮球和足球子类。
最后看使用
通过上例可以看出,使用观察者模式可以动态的扩展类的职责,动态的组合类的各个功能,当看代码时发现一个类既继承了父类同时又持有父类的对象指针,那这基本上就是装饰器模式。
确定好抽象部分和实现部分,将抽象部分和实现部分分离,使得他们可以独立的变化,实现系统和抽象系统可能各自都会有各自的变化,使用桥接模式可以更加灵活的扩展,方法:用组合代替继承。举例:一个图形基类,可以有圆形矩形多边形等等,每个都需要从图形基类中继承出一个子类,但是每个子类还需要画图,画颜色,圆形可能需要黄色黑色白色蓝色等等,矩形也可能需要黄色黑色白色蓝色等等,这种如果单纯的使用继承就不太灵活,可以使用桥接模式,把图形种类当作抽象部分,画颜色当作实现部分,使用组合的方式将抽象部分和实现部分分离,上代码:首先将实现部分(画图)提取出来,同时多种画图方式从这个基类继承后重写,之后会作为Shape图形类的一个成员变量
上面定义了两种颜色的实现方式,红色和黑色,下面定义图形的类,定义一个图形基类,持有画图实现的句柄,之后定义多个继承图形基类的子类,圆形子类和矩形子类。
通过桥接模式可以更好的应对变化,应对抽象和实现的多种组合变化。
工厂模式属于创建型模式,主要用于创建对象时不向外部暴露创建逻辑,通过一个共同的接口指向新创建的对象,通过面向对象的多态,将创建对象的工作延迟到子类执行,由子类决定实例化哪个对象。用于隔离对象使用者与其具体类型之间的耦合关系,当具体类型经常变化时,可以考虑使用工厂模式。有一个类型的抽象基类,同时又有很多继承该抽象基类的具体类型,我们做的就是使其依赖于抽象而不依赖于具体,实现方式是创建一个工厂基类,在为每个具体类型定义一个可以创建其相应对象的工厂,每一个具体类对应一个具体工厂,工厂类继承自工厂基类,通过工厂基类的多态性就可以决定创建什么类型的对象。上代码:
可以通过游戏工厂选择创建不同游戏类型的对象
当有新类型增加时,需要添加一个具体类和一个相应的创建工厂,尽管减少了耦合度,但是其实还是比较麻烦的。
和工厂方法模式类似,不做过多介绍,说一下定义,抽象工厂方法模式主要提供一个接口,让该接口负责创建多系列“相关或相互的对象”,无需指定具体的类,系列对象指的是某一特定系列下的对象间有相互依赖或相互作用的关系,不同系列的对象之间不能相互依赖,如果没有多系列对象创建的需求变化,没必要使用抽象工厂方法模式,使用简单工厂方法模式就可以,拿上一个举例继续说,游戏类型是一个系列,我们有了一个游戏类型的工厂,以后可能会再加入娱乐圈类型的一个系列,那就在做一个娱乐圈类型的系列工厂,以后可能还有文艺类型的系列,那就再加一个文艺类型的系列工厂,这就有了三个系列的工厂,所以就可以在这三类工厂的基础上再抽象出一个抽象的超级工厂,根据不同需求选择实例化哪一个系列的具体工厂,再创建具体工厂下的具体类型的对象。
用于创建重复的对象,定义一个clone接口,通过调用clone接口创建出与原来类型相同的对象,上代码:
单纯看game不知道它是什么类型,它可能是篮球游戏也可能是足球游戏等,如果想创建一个与它相同类型的对象就可以使用原型模式其实就是实现一个clone接口,如果一个对象的拷贝构造函数比较复杂而自己不想使用拷贝构造的方式创建对象也可以使用原型模式,使用方式见上例。
用于构建一个复杂的大的对象,一个复杂的对象通常需要一步步才可以构建完成,建造者模式强调的是一步步创建对象,并通过相同的构建过程可以获得不同的结果对象,一般来说建造者对象不是直接返回的,与抽象工厂方法区别是抽象工厂方法用于创建多个系列的对象,而建造者模式强调一步步构建对象,并且构建步骤固定,举例:想要构建一个老师类的对象,老师有第一技能和第二技能,第一技能是数学就代表是数学老师,第一技能是英语就代表是语文老师,构造一个老师对象需要先设定老师的第一技能再设定老师的第二技能,强调一步步设定技能,将这一步步的构建过程可以抽象出建造者模式。首先定义老师类
定义一个老师的抽象构建器,再根据具体老师继承出具体的老师构建器
定义一个稳定的Director类,由它根据具体的老师构建器决定构建哪一个老师
使用方法如下:
通过Director利用不同的具体构建器都采用相同的步骤一步步构建出不同的具体的老师类对象。
不多说了,网上太多了,列一段单例的代码:
通过共享技术有效支持大量细粒度的对象,主要解决面向对象代价问题,通过共享有效降低创建的对象个数,类似于对象池。举例:篮球分多种颜色,我想要蓝色的篮球想要红色的篮球这就需要创建两个对象,当我再想要一个蓝色的篮球如果能够继续使用之前的那个对象就更好,通过享元模式可以做到。
感觉外观模式没啥特别的,感觉就是做好功能之间边界的划分,做好封装,弄清楚哪部分是稳定的,哪部分是变化的,对外稳定松耦合,对内迭代变化高内聚,子系统的内部外部要做好解耦,为子系统的一组接口提供一个稳定一致的(界面),子系统中的任何变化都不会影响这个(界面)的变化。
为其它对象提供一种代理以控制这个对象的访问,一般对于比较大的对象访问比较困难或者或带来很多麻烦,例如裸指针的使用,所以一般使用智能指针来控制裸指针,使用智能指针也是一种代理模式,举例:A喜欢B想送给B礼物,但是却不认识B,而C却认识B,所以可以把C作为A的代理去送给B礼物,代码:
如上述代码,通过代理模式就可以给丽丽送礼物。
太常见了,每个人都会用,两个不兼容的接口之间的桥梁,就像耳机转换头,充电器转换头等等都是适配器模式,将一个类的接口转换为客户希望的另一种接口的形式,使得原本由于接口不兼容而不能一起工作的类可以一起工作。
使用一个中介对象来封装一系列的对象交互,当多个对象间互相引用且操作比较复杂时可以考虑使用中介模式。如下图所示:左侧五个对象互相依赖,通过中介这个桥梁就可以减少这个依赖。
当一个对象的行为依赖于它的状态并且其有很多种状态而且将来还会有更多状态时,如果使用简单的if-else来增加新状态就违反了面向对象的开闭原则,这时可以考虑使用状态模式,将具体的状态做出一个抽象类,也类似于工厂模式,将具体的状态分散于各个子类中,避免了更多的if-else分支,上代码:
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。