第一章 程序设计与C++语言初步
- 算法:用来解决问题的、由多个步骤组成的具体过程称为算法;
- 实体
- 除了数学问题外,现实生活中的许多行为也可以用算法来表示。如行为:从银行账户中取款,行为中就会涉及到相关实体“银行账户”;
- 实体是从事物内在结构的角度出发;行为则是从事物外部表现出来的功能出发;
- 编译程序和解释程序的区别:前者是将整个高级语言程序翻译成及其语言,最后再执行。后者是逐句的翻译,每翻译一句就执行一句;
- 程序设计是一种编写计算机程序的活动,是为了解决某一特定问题而构造一种专门工具的活动;
- 结构化程序设计:主要技术是自顶向下、逐步求精、采用单入口/出口的控制结构;
- 面向对象程序设计:它是建立在结构化程序设计上的,最重要的改变是程序围绕被操作的数据来设计,而不是围绕操作本身;面向对象更偏向于将程序设计为一组相互的协作的
对象
而不是一组相互协作的函数
; - 一种语言主要由语法的两部分组成:语法和语义;
- 语法刻画的是什么样的符号串可组成一个有效的程序,根据语法描述就可以判断任何一个程序是否符合规定的语法;
- 语义描述的是用一种语言编写程序的含义,就是这个程序要做什么;
- 定义语法常用的定义方法是Backus - Naur(简称BNF)和语法图;
- BNF中规定了一些符号作为描述语法的机制,叫做元符号;
- “:==” 表示定义为;
- “|” 表示“或者”;
- “[...]” 表示任选,方括号的内容可以出现一次或者不出现、 {....} 表示重复, 括号内的内容可以出现零次或者任意多次;
- 语法图比BNF更加直观,可以简单的讲一个BNF转换为语法图;有点类似于电路图;
一个程序是实体的属性和行为在计算机中的表示;C++中用类来描述实体,用来描述实体都具有的属性和行为。C++中的对象表示单个实体;
C++语言是
混合型面向对象程序设计语言
,因为除了类之外还可以包括游离于类之外的函数;Java 就是纯面向对象程序设计语言;C++程序的运行过程
- 1.编写源代码Source Code,.cpp文件;
- 2.将源代码通过编译程序生成目标代码 objective code,.obj文件;
- 3.通过链接程序将目标代码文件生成可执行文件,.exe文件;
面向对象程序设计的过程就是一个建模过程,模拟现实世界或思维世界的各种现象;将想象抽象成实体概念,再将实体概念建模形成一个个类,再通过将类具体化成对象就可以模拟现实世界的各种行为;
面向对象程序设计的特征:数据封装(封装对象行为的实现细节)、继承(模型化一般和特殊的关系)、多态性(一个名字,多种语义)
第二章 基本数据类型
C++有四种基本类型:字符类型、整数类型、浮点类型、空值类型;还有四种复合数据类型:指针类型、引用类型、函数类型、自定义类型/构造类型;其中的构造类型包括:数组类型、枚举类型、结构体类型、共用体类型、类类型;
C++中有五种单词:保留字(int float)、标识符(变量名)、常量(字符常量、数字常量)、运算符(= + - > <)、分解符(;);
常量和变量
- 常量就是在程序执行过程中值不能被改变的量,而变量的值可以改变;
- 在程序中使用变量时,必须遵循
先声明后使用
的原则;
- 输入和输出
- 输入就是从外部设备获得数据;在C++中使用 std::cin >> 变量名,默认输入源是键盘;
- 输出就是将数据送到外部设备;std::cout << 数据, 默认输出到控制台;
- C++ 基本数据类型的存储方式:
数据类型 | 类型名称 | 占用字节 |
---|---|---|
字符类型 | char | 1 |
整数类型 | int | 2 |
浮点类型 | float | 4 |
双精度类型 | double | 8 |
空值型 | void | 0 |
- 隐士类型转换
- 在进行算数运算、逻辑运算和关系运算时,除了unsigned short外,所有的char、short类型的数据无条件转换为int类型后才参加运算
- 隐式转换原则是以数据类型较大的左右转换准则;
- C++使用文件作为划分模块的主要机制;
第三章 基本控制结构
比较简单 没什么需要记录的;
第四章 函数
- 函数的调用过程
- 当在一个A函数中调用另外一个个函数时,要求在程序流转到被调用函数B之前,会先记下A函数当时正在执行的指令的地址和上下文环境,以便在B函数调用完后,程序流程能够回到A函数的地址出,并根据上下文信息恢复执行环境,然后继续执行函数;
- 内联函数
- 由于函数可能会被频繁调用,使得上下文环境切换带来一定的时间和空间上的花销,为了提升执行效率,出现了内联函数;
- 在编译时将所调用函数的代码直接嵌入到调用函数的调用处,而不是将流程转换出去,也就不用切换上下文的函数就是内联函数,有点类似于宏定义;
- 使用关键字inline 修饰定义函数;
- 内联函数不能包含复杂的控制结构,而且内联函数由于是代码替换会增加程序的代码长度;
- 函数参数的缺省值
- 就是允许给函数的形参设置默认值;
- 当函数有多个形参时,设置默认值必须从右向左定义,并且在一个缺省参数的右边不能还有未指定默认值的参数; 如:int func(int i = 8, char asm, int j = 10); 这种缺省值设置就是错误的
- C++的变量有四种存储类型:自动变量、寄存器变量、静态变量、外部变量;
(1)自动变量只能修饰局部变量,有关键字auto 修饰,所有为声明的未指定类别的局部变量都是自动变量;
(2)寄存器变量用register关键字修饰,说明将这个变量的值存到寄存器中使用,以提高和修改该变量的时间效率。因为寄存器是是在CPU中,当CPU中的寄存器超限时,寄存器变量会自动转换为自动变量。常用于循环变量和数组下标等频繁使用的变量;
(3)静态变量既可以是局部变量也可以是全局变量。但无论是那种情况,静态变量都具有全局生命周期,直到程序运行结束;静态变量初始化值默认是0,静态局部变量的初始化是在程序运行中第一次经过它的时候完成,初始化只做一次;声明静态变量使用static修饰;作用域仅限于文件内使用;
(4)外部变量分为定义性声明和引用性声明,前者是带有初始化,后者是未初始化;一个全局变量默认就是定义性声明外部变量,未初始化则初值为0;带有extern声明的定义性变量必须是全局变量,而带有extern的引用性变量既可以是全局的也可以是局部的;它的初始化工作在执行main函数之前就已经完成;一个变量必须且只有一个定义性声明,但是有可以有多次引用性声明;
如果声明自动变量和寄存器变量时没有进行初始化,则它们的初始值是随机的;
当一个块内声明的局部变量屏蔽了同名的全局变量时,可以用作用于运算符:: 访问被屏蔽的全局变量;
预处理命令
- 以#开头,一行写不完可以用“\”号些一行的结尾,就可以继续写下一行;
- 常用的预处理命令:文件包含预处理命令(#include)、宏定义预处理命令(#define)、条件编译预处理命令(#ifdef #else #endif);
- 常用库函数
- 数值函数库 math.h
abs labs fabs 分别返回int long double类型参数的绝对值
ceil(x) 返回大于或等于x的最小整数
floor(x) 返回小于或者等于x的最大整数
fmod(x,y) 返回x/y后的浮点余数,不管y是否为负数,返回值的符号都随这x的符号返回;
pow(x, y) 返回x的y次方;
sqrt(x) 返回x的平方根;
cos(x)返回x的余弦值
sin(x) 返回x的正弦值
tan(x)返回x的正切值
- 字符函数库 ctype.h
islower 是否为小写字母
isupper 是否为答谢字母
isdigit 是否为数字
isalnum 是否英文或者字母
iscntrl 是否为控制字符(ASCII 0x00 ~ 0x1F)
isprint 是否为打印字符(0x20 ~ 0x7E)
isgraph 是否为空格外的打印字符
ispunct 是否为标点字符(除空格、字母和数字外的可打印字符)
isspace 是否为空格、回车、换行、水平制表符、垂直制表符或换页符(0x09 ~ 0x0D或者0x20)
tolower 返回对应的小写字母
toupper 返回对应的大写字母
- 伪随机数函数,在库stdlib.h
rand 返回一个伪随机数
srand(seed)通过当前时间time(0)作为种子 长生随机数
- 模板函数
- 为了让函数的形参和返回值类型适应多种类型;类似于泛型;
- 写法:直接在一个函数上面加上:template <class T>;
template <class T, typename D>
T square(T x) {
return x * x;
}
第五章 类与对象
- 类是一组具有相同属性,表现相同行为对象的描述;
- 类成员有三种访问控制方式:public、protected(默认行为)、private;
3.定义一个类的写法:
C++中通常类名通常用大写字母表示
class 类名 {
public:
公有数据和函数
private:
私有数据和函数
};
- 类中的任何数据成员在声明不允许使用auto、register、extern关键字进行修饰,但允许使用static;
- const 不仅可以修饰数据成员也可以修饰成员函数,成员函数的const加在函数末尾;
常量函数中可以改变局部变量、全局变量或其他类对象的值,但不予许修改本对象数据成员的值;
只有类的函数可以成为常量函数;
int get_value() const;
int get_value() const {
}
- 类实现文件中的函数定义形式:
返回类型 类名::函数名(参数) {
函数体
} - 在将类文件和类实现分开,或者在他文件模块引用其他的类时,需要使用MAKE和PROJET工具将类实现文件的目标链接在一起才能生成可执行文件;因为使用的是VSCode,在mac下配置环境整不明白,只能自己写Makefile了;
以下示例使用make工程管理器,链接多文件编译
定义三个文件模块:CIRLE_NUMBER.hpp CIRLE_NUMBER.cpp main.cpp
/*
循环计数器
*/
class CIRCLE_NUMBER {
public:
void set_model(int min, int max){
min_value = min;
max_value = max;
current = this->min_value;
return;
}
void increment();
void decrement();
int get_current_val();
void set_current_value(int value);
private:
int max_value;
int min_value;
int current;
};
#include "CIRCLE_NUMBER.hpp"
void CIRCLE_NUMBER::decrement() {
}
void CIRCLE_NUMBER::set_model(int min , int max) {
min_value = min;
max_value = max;
current = this->min_value;
return;
}
void CIRCLE_NUMBER::increment() {
int model = max_value - min_value + 1;
current = (current - min_value + 1) % model + min_value;
}
void CIRCLE_NUMBER::decrement() {
int model = max_value - min_value;
// 当current == min_value 时 加上model不至于为负数
current = (current - min_value - 1 + model) % model + min_value;
}
int CIRCLE_NUMBER::get_cuurent_val() {
return current;
}
void CIRCLE_NUMBER::set_current_value(int value) {
current = value;
}
#include <iostream>
#include "CIRCLE_NUMBER.hpp"
using namespace std;
int main() {
CIRCLE_NUMBER circle;
circle.set_model(10,20);
circle.increment();
circle.decrement();
circle.decrement();
cout << "current == " << circle.get_cuurent_val() << endl;
return 0;
}
创建makefile文件,如:my_makefile,在执行make的时候会自己寻找叫makefile的文件,如果是其他名字 需要使用make -f 文件名
;
内容如下:
在使用VSCode编辑Makefile时,有可能命令的缩进不能识别,使用时可能报缺少分割符,使用vim编辑TAB即可解决
;
objects = practise2.o CIRCLE_NUMBER.o
exe: $(objects)
g++ -o exe $(objects)
.o : main.cpp
g++ -c main.cpp
CIRCLE_NUMBER.o: CIRCLE_NUMBER.cpp
g++ -c CIRCLE_NUMBER.cpp
clean:
rm exe $(objects)
- 类类型的变量就是对象,对象通常用小写表示;
- 访问对象的成员使用选择运算符".";
- 构造函数
- 如果一个类没有定义构造函数,那么C++程序会自动为类创建一个构造函数;
- 定义跟java一致,“ 类名 () {函数体}“;
- 在实现构造函数时,必须在类中先声明;
- 在初始化对象成员时,如果成员有对象成员的属性,那么在初始化的时候必须在调用构造函数的时候初始化成员对象。在构造函数()后写一个“:”,再调用对象成员的构造函数进行初始化;如果有个就用逗号隔开;
- 除了对象成员,基本数据成员和常量数据也可以用初始化列表在构造函数后进行初始化;尤其是常量成员只能使用初始化列表进行初始化;
CIRCLE_NUMBER::CIRCLE_NUMBER(): count(55), constVariable(100),min_value(10) {
std::cout << "倒计时器 构造函数被调用" << std::endl;
return;
}
- 全局对象和静态对象的构造函数在main函数执行之前就被调用,局部静态对象的构造函数是当程序第一次执行到相应的声明语句才被调用;
- 析构函数
- 在一个对象被销毁时调用,写法只是在构造函数前面加个“~”符号
- 在实现析构函数时,必须在类中先声明;
- 基本数据类型的变量和对象变量本质上是一样的,基本数据类型也可以使用对象的初始化方式;如:int test(10);
- 重载是允许一个运算符或函数名字具有多重含义的机制;
- 在一个类文件模块访问其他的类,可以用class 声明的方式;但是声明成员对象必须使用指针变量。如果使用include方式就不需要;
- 友元
- 友元类是为了让某个类能够访问当前类的受保护成员的方法,用关键字friend class 类名 定义;这样被定义为友元的类就单方面的成为了当前类的朋友;
- 友元函数作用跟友元类差不多,只是被限定在某个函数内访问私有成员;定义方法:friend 返回类型 类名::方法名(参数列表);
第六章 复合数据类型
- 一个变量有四个要素组成:一个名字(标识符)、一个属性(类型)、一个关联(变量的内存地址)、一个值(变量内存地址存放的值);
- 变量的关联部分叫左值,变量的值部分叫右值;
- 指针
- 就是专门用来存放其他变量地址的变量,也就是指针变量的值存储的是一个指针地址;
- 指针可以指向一个没有名字的数据值;
- C++语言中的所有数据类型都有相应的指针类型;
- 声明指针时就是在类型的后面加一个指针,指针是用来修饰类型的,并不是变量名字的一部分;
- 一个指针变量占用的存储空间大小取决于机器的寻址方式;
- 声明指针变量时,不会自动初始化指针变量,是指向一个随机的存储单元;
- 取出指针变量的值,只需要在指针变量前加“”,也可以赋值。如:age = 100;
- 取址符“&”,可以取出一个变量的地址;
- 空指针NULL,值为0x00;
- 指针变量如果为初始化,那么它会随机指向某一内存区域;也就是指针变量在初始化要么为空指针要么指向另外一个同类型变量的地址进行初始化;
- 指针变量只能做加减运算,每一个操作都相当于指针变量的地址值会按照类型的所占用的字节数移动;
- 引用类型是C++为了提供按引用调用的参数传递方式,在类型的后面加一个取址符“&”,就可以引用同类型的其他变量了;
- 如果想要 引用变量不得进行修改就在引用类型的前面加一个const;
- 如果const放在指针前面,表示指针地址指向的值不可改,如果放在*之后表示指针变量的地址不可更改;
// 下面三种变量都是指向的同一个age1
int age1 = 100;
int *age = &age1;
int& quote_age = *age;
quote_age = age1;
cout << "指针地址:" << age << " " << *age << endl;
*age = 230;
quote_age = 199;
age1 = 50;
cout << "指针地址:" << age << " " << *age << endl;
- 数组
- 数组的声明-- 类型 数组名[常量表达式],常量表达是也可以不写直接在定义的时候确定数组长度, 跟Java的区别是java中的数组括号中不能写常量表达式,只能在定义的时候获取数组的长度;
- 不允许动态确定数组的长度;
- 如果数组长度声明时5,但是在初始化时只初始化了其中几个,那么其他都会初始化为0;
- 数组的初始化只有一次,之后修改都只能单个元素去修改;
- 数组在函数中的作为参数是按照引用传递的,如果想要限制数组不被修改,用const修饰数组形参;
- 数组名字也是一种指针类型,但它一种常量指针类型,其地址值是不可改变的;
指针数组是指数组中的元素是指针变量;如:int * array[10],也可以不写字符个数,在定义的时候自动识别;
数组指针是指基类型为数组类型的一个指针;如:int (array)[10],因为运算符[]的优先级高于指针运算符,所以array是一个指向数组的指针变量;
字符串常量
- ""双引号就是用来存放字符串常量,每个字符串的最后都会默认加一个字节存储结束符“\0”,他不会计入字符串长度,只是会使占用的的空间增加一个字节;
- 字符串变量
- 字符串变量是一个基类型为字符类型的数组变量,同样需要遵循先声明、后使用的规则;
- 定义:char 数组名[元素个数] 或者 char *数组名(char *的方式在新本版中已经弃用),还有一个区别是前者是分得了存储空间,后者没有;
- 字符串常用常用函数的库是<string.h>,有获取长度strlen、复制strcpy、拼接strcat、比较strcmp、寻找位置字符strchr、查找字符位置strstr
- 准换函数库<stdlib.h>,字符转整型atoi、转实数atof;
- man函数中的数组参数中的第一个函数是指向命令和路径的,所以实际个数argc是实际命令行参数 + 1;
- 对象指针
- 指针的基类型是一个类类型,指针指向的是一个对象;
- 声明对象指针时只是分配了存放指针值的存储空间,并没有分配存放实际类型的对对象空间;
- 对象指针访问成员的运算符是"->";
- 在类的成员函数体重,都隐含提供了一个对象指针,这个对象指针是指向该成员函数正在操作的对象,用关键字“this”;
- 动态创建和撤销对象的关键字是new和delete,也包括基本数据类型;
- 动态申请存储空间可能会失败,在内存空间不足的情况就会返回一个空指针;
- 由于动态申请的内存在堆区需要程序员自己管理,所以需要使用delete关键字动态撤销释放空间,但是指针指向的地址还是原来的地址而不是空指针;如果撤销是的对象数据,则需要在变量前面加[],确保能调用数组所有对象的析构函数;;
10.对象的复制 - C++默认提供的对象复制策略是浅复制,使用运算符“=”,如果对象成员中有指针对象,那么在浅复制时,只是复制了对方的指针,并没有生成新的指针,当一个改变时,另一个就会出现问题;
- 在深复制时需要自己定义相关函数,或者重载预算福“=”;
11.函数指针
- 是指一个函数的入口地址,就是执行一个函数时,首先被执行的第一条指令所存放的存储单元地址;
- 函数在编译时被分配了一个入口地址作为函数指针的值,通过函数指针也可以调用一个函数;
- 函数指针的定义:函数返回类型 (* 函数指针名)(形参表),跟OC中的block一毛一样,只是*会变成^;
int fuctionPoint(int a, float fb) {
return 99;
}
int main() {
int (* variable)(int, float);
variable = fuctionPoint;
variable(88,55.5);
}
- 函数指针可以函数的参数,可以达到类似于一个回调的功能;
- 结构类型struct的作用与class非常相似,其区别在于没有成员访问控制访问方式;class的缺省是private,struct的是public;
第七章 继承机制
- 继承的语法是分号“:” 继承访问控制 父类{};继承访问控制部分是可选,用于规定基类成员在派生类中的访问控制方式。即基类成员在派生类中是公有的、受保护的还是私有的;缺省是private;
2.即使父类的私有成员变量,子类也会分配父类的私有成员;派生类在继承时,成员访问控制声明为public,才能支持多态; - 由于公有派生类型可以兼容基类型,所以指向基类对象的指针的指针变量也可以指向其公有派生类的对象;
- 在编写面向对象程序时,应尽可能使用指向类类型对象的指针,而少用对象本身。特别是作为函数参数传递与函数返回值的时候,直接使用对象会引起程序语义的复杂化;
- 派生类不继承基类的构造函数和析构函数,相反派生类的构造函数必须提供实际参数信息给基类的构造函数;
- 创造一个派生类的对象时,如果基类有构造函数则会先调用基类的构造函数,再调用派生类的构造函数;对象销毁时,析构函数的调用顺序则相反;
- 如果基类的构造函数带有参数,那么在派生类构造函数实现时必须使用初始化列表的方式将参数传递给基类;如果没有显示调用基类的构造函数,则默认调用积累中形式参数为空的构造函数;
- 使用private或者protected继承访问控制时可以屏蔽从基类继承下来的公有成员,这时可以使用访问声明将一些被屏蔽公有成员恢复到原来的访问控制状态;一般形式为:基类名:: 成员名,这样声明在派生类中的相关区域中;注意:重声明原有成员时,只能恢复原来的成员访问控制,并不能改变;
- 解决重复集成二义性问题的方法是
虚基类
,虚基类可以保证在任何派生类中只提供一个基类的副本; - 派生类在继承时,可以规定基类为虚基类。在成员访问控制符前面 加一个virtual关键字;如: class 派生类: virtual public 基类名;
- 对于虚基类构造函数的调用总是先于普通基类的构造函数调用;
第八章 多态性
- 多态性分为静态和动态两种,前者是编译时决定的,后者是运行时决定的。如用父类指向子类,在调用方法时调用的依然是父类的方法。如果将要调用的函数改成虚函数就会变成调用子类的方法;虚函数用virtual 修饰;
- 运行时多态性是指必须等到程序动态运行时才可确定的多态性,主要通过集成结合动态绑定获得;普通函数使用的是静态绑定,而虚函数使用的是动态绑定;
- 重载函数暗示了一种关联,我们不应该重载哪些本质上有区别的函数,只有当函数实现的语义非常相近时才应使用函数重载。重载是通过形参的个数和对应位置的类型来区分的;
- 拷贝构造函数
- 当使用对象本身作为形参或者是返回值时,在传递的时候会临时创建一个对象副本,创建副本对象时并没有调用构造函数,只会调用析构函数,当函数作用结束后这个对象的被销毁了;解决办法是将传递方式改为按引用调用,或者定义一个拷贝构造函数;拷贝构造函数定义如下:
class_name::classname(const class_name& object) {
}
- 如果一个类没有定义拷贝构造函数,那么编译程序将自动产生一个公有的缺省拷贝构造函数。缺省拷贝函使用浅复制方式逐位复制;
- 当类的数据成员中包含指针时,除了必须自定义拷贝函数以外,还必须自定义析构函数和重载赋值运算符“=”;程序员仅可重载一元或者二元运算符;
- 在声明一个对象作初始化使用的运算符“=”,会调用拷贝构造函数;
- 虚函数
- 就是在一个类中用保留字virtual定义的成员函数。积累的虚函数在派生类中中仍然是虚函数,并且一般需要在派生类中重定义;
- 在派生类中重定义继承的虚函数时,即使没有保留字virtual,它仍然是虚函数;
- 一个含有虚函数的类成为多态类;
- 动态绑定必须使用基类对象指针来访问虚函数才可实现;也就是必须定义个基类对象的指针(基类 * obj)指向派生类对象,再用基类指针去调用虚函数,才会产生多态的效果;
- 纯虚函数
- 是在基类中声明但在基类中又没有定义的虚函数;
- 任何派生类都必须重定义基类中的纯虚函数;
- 定义形式:virtual 类型 函数名(参数表) = 0;
- 如果一个类中至少有一个纯虚函数,那么这个类被称为
抽象类
;
- 抽象类
- 抽象类必须是派生其他类的基类,而不能用于直接创建对象实例;
- 作用:抽取若干类的共同行为,形成更清晰的概念层次;
第九章 类属机制
- 类属类
- 由模板定义的类成为类属类。就像其他语言的泛型;类属类不是一个真正的类类型,不能用类进行实例化,需要加上类属参数;
- 在实现类属类的函数时,必须带上模板和类属类参数;
- 类属类实例化:类属类名<类属参数类型> 变量 = ...;
- 在使用类属类时,防止声明被多次包含进行编译,产生重复说明错误,可以使用预编译指令“#ifndef #define #endif”;
#ifndef __GPERSON_HPP
#ifdef __GPERSON_HPP
template <class T, class D>
class Person {
private:
T test;
};
#endif
- 类属函数
- 定义类属函数应注意的是由保留字template给出的每一个形式类属参数都必须出现在函数参数中;也就是必须用于形参;返回值时可选的;
- 在函数调用时,会先查找是否有参数类型相匹配的重载函数;
第十章 输入/输出流
- 文件
- C++ 语言将文件看做是一个字符的序列,即由一个个字节的数据顺序组成;
- 根据文件数据的组成形式,可将文件分为文本文件和二进制文件;文本文件中的每一个字节存放的是一个ASCII码,表示一个字符。二进制文件可以是任何数据类型的文件;
- 流类库
- 基类是ios,派生类时istream和ostream,在这两个类下有派生出iostream;
- streambuf是最低级的类,负责管理一个流的缓冲区,在ios基类中保留了一个指针指向了streambuf类的对象;
- 流类库预定义了四个流对象:cin、cout、cerr(无缓冲的标准错误输出)、clog(有缓冲的标准错误输出);