1.入门小语法
1.1 命名空间
当想使用库文件的某个函数时,为了防止由于重名而引起的混乱调用,使用命名空间来区分同名函数。
字符串String也是标准命名空间的一个。如果没有using namespace std;
想用string 类型得 std:: string
1.2浮点数的存储
如果定义7298为一个浮点数,那么计算机存储它时,会自动将它存储为7.298 e3
1.3 作用域
一个大的花括号被称之为一个块,这个块被称作一个作用域,如果在这个块里声明一个变量,该变量只在这个花括号里有用。
如果该变量定义在main函数外,则它的作用为全局。
1.4 变量命名规则
C++的关键字有以下几个
variable
type
function
namespace
template
class
1.5 引用
当写成int &时,我们可以修改外面那个变量的值。
这里和指针还是有一点区别的。
引用就是对内存的操作。
例如
int b=1;
int & aa=b;
aa=2;
cout<<b<<endl;
输出的结果为2.
b的值也会改变,这个引用相当于指向那块内存。
引用的作用就是改变函数外变量的值
C++通过函数传对象的时候,改变对象里的属性值,如果不用引用,没办法把这个值传出去。
1.6 函数重载
就是根据传进来的参数自动选择类型,函数名相同才能重载。
可以根据数据传进来的类型来选择执行方法。
1.7 字符串
引入了#include<string> 我们可以用+号来连接字符串了。
1.8 枚举类型
一个人物可能有很多种状态
里面的实际上从第一个开始都是常量,如果不赋值,默认为第一个为0,第二个为1,默认以此叠加。
如果第一个赋值为1,后面不给值,则默认增加1。
这里面的变量实际上是常量的别名。
定义一个枚举类型变量,并给它赋值,它的赋值必须得是枚举类里声明的常量,不能直接赋值为1。
然后我们可以在switch里面使用这个枚举类。完成动作设计。
1.8.1 同名枚举类属性区分
如果两个枚举类里的变量同名,我们调用的时候需要区别对待。
1.9 指针
指针可以提高效率,加快速度。
为什么说它高效呢?
结构体的定义和赋值占用很多字节的空间。
因为我们的方法里如果需要一个类似learn(Student stu),需要创建一个临时结构体类型局部变量Student来传进我们的函数中,这样无疑是多花费空间。
我们要做的就是传地址进函数,指针是有类型的,当你用*读该指针的地址时,就会读到同样类型长度的变量。
2 类和对象
C++类里的属性和方法默认是private私有的,想在别的地方调用方法,需要放在公共空间。
结构体里的默认权限为public
类内初始化变量,必须得用const。
2.1 类和结构体的区别
2.1.1 C和C++中结构体的区别
(1)C语言中的结构体不能为空,否则会报错
C语言中要求一个结构或联合至少有一个成员。C语言中,空结构体的大小为0,而C++中空结构体(属于空类)的大小为1
(2)C语言中的结构体只涉及到数据结构,而不涉及到算法
在C语言中数据结构和算法是分离的。换句话说就是C语言中的结构体只能定义成员变量,但是不能定义成员函数
然而在C++中既可以定义成员变量又可以定义成员函数, C++中的结构体和类体现了数据结构和算法的结合
不过虽然C语言的结构体中不能定义成员函数,但是却可以定义函数指针,不过函数指针本质上不是函数而是指针,所以总的来说C语言中的结构体只是一个复杂数据类型 ,只能定义成员变量,不能定义成员函数,不能用于面向对象编程
(3)C语言中定义结构变量时不可省略struct关键字,C++可以省略
在C语言中使用struct定义的结构体中在定义变量的时候,struct不能省略
但是在C++之中则可以省略struct
2.1.2 C++中结构体和类的区别
C++中的struct对C中的struct进行了扩充,它已经不再只是一个包含不同数据类型的数据结构 了,它已经获取了太多的功能
struct能包含成员函数吗? 能!
struct能继承吗? 能!!
struct能实现多态吗? 能!!!
既然这些它都能实现,那它和class还能有什么区别?
最本质的一个区别就是默认的访问控制:
默认的继承访问权限:struct是public的,class是private的
C++中,不使用结构体丝毫不会影响程序的表达能力。C++之所以要引入结构体,是为了保持和C程序的兼容性
但有时仍会在C++中使用结构体,是因为可以使用结构体将不同类型数据组成整体,方便于保存数据(若用类来保存,因类中成员默认为私有,还要为每个数据成员特定函数来读取和改写各个属性,比较麻烦)
struct可以继承class,同样class也可以继承struct
struct是一种数据结构的实现体,虽然它是可以像class一样的用。我依旧将struct里的变量叫数据,class内的变量叫成员,虽然它们并无区别
2.1.3 总结
概念:class和struct的语法基本相同,从声明到使用,都很相似,但是struct的约束要比class多,理论上,struct能做到的class都能做到,但class能做到的stuct却不一定做的到
类型:struct是值类型,class是引用类型,因此它们具有所有值类型和引用类型之间的差异
效率:由于堆栈的执行效率要比堆的执行效率高,但是堆栈资源却很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理作为基类型对待的小对象,而class来处理某个商业逻辑
关系:struct不仅能继承也能被继承 ,而且可以实现接口,不过Class可以完全扩展。内部结构有区别,struct只能添加带参的构造函数,不能使用abstract和protected等修饰符,不能初始化实例字段
牛人总结:
(1) 在表示诸如点、矩形等主要用来存储数据的轻量级对象时,首选struct
(2) 在表示数据量大、逻辑复杂的大对象时,首选class
(3) 在表现抽象和多级别的对象层次时,class是最佳选择
2.2 声明对象时,在构造器里完成有些属性的初始化。
这样可以在该对象被创建的时候,就发出叫的动作。这里跟java一样
2.2.1 在类外解释类的方法
用双冒号,来编辑类或结构体里的方法。
结构体也可以这么写构造函数。
不加返回值类型的是构造函数。在声明对象时,构造函数会启动。
2.3 继承
C++的继承类书写。
继承可以有3个keyword,public,protected和private。
使用public构建子类时,原封不动的继承父类的变量与函数的访问级别。
使用protected构建子类时,父类中任何的public级别的变量和函数,在子类中都将变成protected级别。
2.3.1 继承带来的性质
当创建子类对象时,自动执行父类的构造方法(空参构造方法)。
在传参数进子类带参构造方法中,想要用父类的带参构造方法,
直接在里面调用父类的带参构造方法。
但是如果我在声明子类对象时,不想自动执行父类的构造方法呢?
在子类的构造方法后冒汗,指明我们要用的父类构造方法,这样就不会执行空参的。
2.3.2 类里修饰符的访问限制
类里的东西默认private。即使是对象想访问方法,也不行,如果对象非要访问,
那就把这个方法放入public空间。
protected比private高级一点,private只能在类里访问属性和方法。
该类的子类可以访问父类的protected空间的方法。
2.3.3 封装思想
pirvate的东西,用 set get的方法操作private属性的值。
3 栈和堆
3.1 栈stack
程序的执行是在栈里。比如进入main之后执行子程序,然后子程序的地址就会入栈,子程序里的局部变量就会入栈,该局部变量失效或者说使用完毕,就会返回地址给function1,然后function1执行完毕,返回地址给main函数。
程序在运行的过程中,栈在不断的增加和删除东西。
3.2 堆heap
使用动态分配的内存。用完之后一定要自己手动释放。
我们在申请一个new int 空间时,里面可能有垃圾数据,这个时候得初始化来覆盖。
p这里是一个地址。
3.2.1 动态释放内存
释放冻结内存,但不会抹去该内存上的数据。可以让这块地址被其他子程序用来存放变量。
这个函数每次被调用都会重新申请一块空间。当程序结束时,p在栈上,会被释放,但是这个空间不会被释放。
如果我们使用了关键字delete,那么p就会指向空。如果我们还对p进行 *p操作,那就会出现空指针异常。我们可以让p指向nullptr关键字。或者让p=0,让p指向0内存地址,但是这样容易被误解
而且可能会在重载的时候发生错误,比如让p=0了之后,将指针p传入方法,方法重载时可能分配给int类型参数的。
4 析构函数
有时对象在被销毁时,同时也需要一些操作。可以来清在对象创建时,或者对象在生命周期内所作的任何事情。
构造函数前面有个波浪线。当delete该对象时,就执行析构函数。
4.1 对象在建立的过程中内存的变化。
比如 man * p=new man();
1.p放在栈上,new申请的空间地址返回给p,然后构造器
2.构造器为里面的变量动态分配内存。
3.当该空间被delete时,调用析构函数,释放成员变量所占内存。
4.2 析构函数的实战操作
delete关键字触发析构函数。
析构函数会在它的作用域结束时运行。
比如这题的作用域是一个花括号
而如果我们这里定义一个静态类变量,会发现,当{}结束后,也不会执行析构函数。
5 静态变量
5.1位于函数中的 静态变量
如果在一个方法里声明一个静态变量。这种类型的变量在函数结束后仍然存在。
它只会被初始化一次,独立于函数存在。
5.2 静态类对象
比如这个dog对象不是静态的,当花括号结束了,这个变量超出作用域,即将被删除。
可是它是一个静态对象,当花括号结束了,它依然存在。
直到主函数结束,它才会被删除
5.3 放在类里的静态变量
类里的静态变量,都是独立于类对象独立存在的。
即使创建10几个对象,该变量只存在一个。
比如这个静态变量,增加的写在构造器里,每创建一个对象,它就会增加1,类似于游戏的玩家数量。
因为这个变量只属于类,不属于任何一个对象,所以得在类外初始化它。
5.4 静态函数
如果一个类里有静态函数,那么直接.以下就可以调用该函数,不用创造对象。
6 重写继承的方法
为了能让函数在不同的派生类中有不同的版本。我们把这个函数设置为虚函数。
可以让子类相同的函数与父类相同的函数有不同的功能。
在子类中我们没有必要再给他定义成virtual,因为它已经继承了父类的virtual权限。
为了方便标识,我们可以多写一个override关键字来标识这是一个重写的方法。
但是如果我想调用它父亲版本的这个函数呢?
方式1:在调用子类重写方法时,同时也调用父类的这个方法,通过完全限定的调用。
7 多态
多态跟继承有关。
用父类的指针指向子类的实例
如果调用相同的函数(这个函数子类也重写了),即便用父类的指针调用这个同名的方法,使用的确是子类实例里的方法。用*号或者用->
一个方法,可以实现同一继承方法的不同作用。
8 多继承
菱形继承,A和B都继承自P,都有各自的f函数。
但C同时继承了A和B这两个类,那现在C想用这个f函数,该怎么办呢?
8.1 方法一,完全限定指明
这样通过完全限定,则可指定用哪个父类的函数。
8.2 方法二,虚继承
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
具体操作如下
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
比如这俩类
比如istream和ostream都有方法f(),
iostream最终只保存了一份方法 f()。节省空间。
9 动态强制类型转换
9.1 隐式转换:编译器的转换
比如 float f=5.0; int a=f; 这个时候就发生了隐式转换。
9.2 显式类型转换
C+可以用C里的强转。但是C风格的强转不会考虑数据的完整性
9.2.1 C+里的动态类型转换(向上转型)
一般用于派生链(即继承关系)
举个例子,继承关系如下。
向上转型时(即像基类转型)有两种方法
这两种使用运算符号dynamic_cast+,尖括号里指明目的类型<p*>。将c转成p类型。
或者用多态来转型。
9.2.2 C+里的动态类型转换(向下转型)
把p类型的转成A类型只能这么转。
这种情况可能会出现转换失败,因为A可能不适合做p的基类。
如果转换不成功会返还一个null。
9.2.3 C+里的动态类型转换(交叉转型)
因为强制类型转换使用运行时的信息,有的编译器就要求打开
运行时类型信息:RTTI :Run-Time Type Identification,来方便强转型的工作。
10 静态强制类型转换(static_cast关键字)
动态强制类型转换工作时,必须使用RTTI,来保证强转的合法性。适用于存在继承关系的类型转换。
非多态就没有虚函数,用static_cast可以在派生链中将一种指针转成另一种指针。
静态强制类型转换,不需要RTTI,
11 constant-cast
比如有一个constant int类型的变量或表达式(这个变量或者表达式一开始的时候就不是constant类型),把它转成int类型的。
12 reinterpret-cast
它不需要检查任何东西,想往哪转往哪转(所以十分危险)
13 头文件