c++学习
一、基础知识
-
bool 非0为真(负数也为真)
-
inline 内联函数,以内存为代码,运行时间加快
-
函数重载:(1)函数名相同 (2)参数列表不同
-
参数缺省:定义给其中一个形参一个默认值,调用时可以少写一个实参值
使用:声明给默认值,调用函数时不给 -
引用:int mum=0;
int& dd=num; &解析引用符
-
命名空间:组织和重用代码的编译单元
Namespace ::作用域符 相当于“里面的”
-
new 相当于 malloc 申请内存
delete 相当于free释放内存
// 1 申请单个内存
int* p1 = new int;
*p1 = 0;
// 2 申请单个内存且初始化
int* p2 = new int(999);
cout<<"*p2 = "<<*p2<<endl;
// 3 批量申请(连续的内存)
int* p3 = new int[10];
for(int i = 0; i<10; i++)
{
p3[i] = i;
cout<<"p3["<<i<<"] = "<<p3[i]<<endl;
}
delete p1;
delete p2;
delete[] p3;
二、面向对象
-
对象在程序内抽象为:属性+行为(即数据+函数)
-
三(四)个关键词
封装(会用就行),继承,多态(不同情况做不同的事),(抽象)
-
在类class的外面访问private,通过一个公有成员函数访问
-
c++结构体允许有函数
-
string类:char ch = str[2]; ch = str.at(1); str.length();str.empty(); 空:ture 非空:false
str.clear(); str == str; 判断是否相同
// 语法
class 类名
{
// 默认是私有的
// 成员:1 数据 2函数
// 访问权限修饰关键字
public: // 共有的
// 成员:1 数据 2函数
private: // 私有的
// 成员:1 数据 2函数
protected: // 保护的
// 成员:1 数据 2函数
};
三、构造函数与析构函数
1. 构造函数
特殊成员函数——创建对象赋初值
(1)构造函数名与类名相同
(2)无返回值类型(例如void),也无返回值(例如return 0)
(3)参数无限制,故可重载
(4)有低保
使用注意:
(1)构造函数在class内声明,在class外实现
(2)构造函数在创建对象时会自动调用(低保)
在c++中const变换时把变量改为常量
在c语言中const时把变量改为只读类型
故而当数据成员必须初始化,但不能在构造函数中幅值,用成员初始化列表的方式给数据成员赋值。(例如const,引用)
public:
MyClass(int i);
...
Myclass::MyClass(int i):id(i)
{
}
2. 析构函数
对象生命周期结束时进行清理(会自动)
(1)析构函数名与类名相同,在前面加一个~
(2)无返回值类型(例如void),也无返回值(例如return 0)
(3)有低保
注意:
(1)因果关系:不是因为调用析构函数,对象生命周期结束;而是因为生命周期结束,调用了析构函数。
(2)任意一个类只有一个析构函数,且在公有属性(public)下。
3.拷贝构造函数
完成一个复制过程,参数是一个类的对象的引用
(1)先得是构造函数,第一个参数是类的引用
(2)有低保:没写系统会给,将成员一一赋值
MyClass(const MyClass& obj){}
构造与析构的顺序:先构造的后析构
调用时机:
(1)使用一个对象对另一个对象初始化
(2)使用一个对象构造另一个对象
(3)函数的参数是类的对象
(4)函数的返回值是类的对象
深拷贝:指针不能直接赋值,不然两指针指向同一内存,需new一个新的内存,然后把值赋入新的内存中
解决方法:声明一个私有的private的拷贝构造函数,只声明不定义,主动让程序报错
四、继承与派生
1.继承与派生
在原来类基础上快速增加新功能,不要影响源代码,即创建新方式——子类(派生类)
继承与派生为同一过程在不同角度的名称
单继承 —— 一个父类 多继承 —— 多个父类
2.继承权限
(1)公有继承public (2)私有继承private (3)保护继承protected
注:继承是子类继承父类的全部成员
公有继承public | 私有继承private | 保护继承protected | |
---|---|---|---|
public | public | private | protected |
private | 不可访问 | 不可访问 | 不可访问 |
protected | protected | private | protected |
多级继承:仅需要分析直接父子关系
3.父子关系
(1)派生类的构成:除过基类函数的构造函数与析构函数以外的部分(因为存在“低保”)
(2)派生类与基类关系:派生类对象可当作基类对象来使用,即用父类的地方可用子类,基类对象不是派生类对象
父类对象可由子类对象幅值(因为父类小,子类大,父类有的子类都有),但是子类对象不能由父类幅值
父类指针可指向子类对象(因为父类指针短,子类指针长),但是子类指针不可指向父类对象
(3)派生类的构造析构顺序与上述一直,先构造的后析构
4.菱形继承
可以采用虚继承的方法,virtual 多了两个虚指针,在给aa赋值时,虚指针自动判断重复只取其中一个,只多了两个虚指针所占用的内存.
五、多态
1.联编
计算机彼此关联的过程(映射)
(1)动态联编:运行时才知道执行什么代码
(2)静态代码
动态联编条件:
(1)拥有虚函数成员的类
(2)有继承关系,并且有虚函数类为父类
(3)用基类指针调用派生类对象(虚函数成员)
2.多态
虚函数:在函数前加virtual,所有虚函数存入虚函数表(有低保),虚函数可以被继承,虚函数表不能被继承,虚函数表记录类里面的虚函数指针(首地址)。
父类析构不虚析构可能会造成仅清理父类内存,清理不干净,使用父类虚析构保证能调到子类大的虚析构。
3.纯虚函数
纯虚函数:没有函数体的虚函数,在子类中实现父类纯虚函数。
virtual void test_func() = 0;
析构函数写纯虚函数得再类外实现(例外)。
抽象类:普通类并且用于一个或一个以上的纯虚函数,不允许实例化对象,即取定义对象,可以去定义指针。
4.final关键字
(1)权限掠夺者:终结操作
(2)掠夺函数权限:组织重写(虚函数)
void test_func() final;
(3)掠夺类的权限:阻止派生(无子类)
class Father final;
纯虚函数:
virtual void test() final = 0;
六、运算符重载
1.重载
赋予运算符不同功能,即定义一个函数写做什么事即重载规则。
// 返回值类型 函数名(形参列表) {}
// 函数名:operator运算符名称
obj_1.operator+(obj_2); // obj_1 + obj_2
obj_2.operator+(obj_1); // obj_2 + obj_1
在类外定义全局函数用到私有对象,可以声明该函数为友元,即在声明前面加friend
一个对象赋值给另一个对象 —— 拷贝构造
其他赋值 —— 赋值函数
2.规则详解
(1)不是所有运算符都可重载
(2)不能改变结合性和优先级
(3)不能改变运算符的用法(目数:单目还是单目,双目还是双目;结合性:操作数在左还是左)
(4)运算符重载函数不能有默认的参数
(5)运算符重载函数可以作为类的成员函数(自己当作this指针,占一个参数的位置,故而参数数量减一,例如双目变单目)
,也可以作为全局函数(无this指针,该双目任然两个参数)
(6)箭头运算符 -> 下标运算符[] 赋值运算符 = 函数调用运算符() 只能以成员函数的形式重载
3.重载输入输出
(1)建议作为友元函数重载friend
(2)istream输入
(3)ostreanm输出
有返回值的调用可以作为其他函数的实参
4.注意事项
(1)语法很简单,主要是规则
(2)可以显式调用(写函数),也可以隐式调用(写运算符)
(3)注意与友元的联合使用
七、模板
1.模板与泛型编程
模板:实现类型参数化,进行传递
泛型编程:与类型无关的逻辑代码
2.模板分类
(1)函数模板:使用函数模板写模板函数
(2)类模板:使用类模板写模板类
3.函数模板
(1)定义
写代码的方式(语法),用来定义模板函数(数据模型通过参数传入)
(2)语法
/*
通过函数模板定义模板函数
template <typename Type_1, typename Type_2,...,typename Type_n>
返回值类型 函数名(形参列表)
{
函数体;
}
函数名 <类型列表>(实参列表);
*/
(3)对比
a. 可重载
b. 模板函数与普通函数重载,先调用普通函数
4.类模板
(1)定义
类的框架,指定数据类型才能定义对象
(2)语法
/*
template <类型参数列表>
class 模板类名
{
成员;
};
类型参数列表: <class T1,...,class Tn>
// 创建对象时指定类型
MyData<char, float> data_3(68, 3.14f)
data_3.shouData();
*/
(3)类模板做函数的参数
// 1
void test_Func_1(MyData<int, double>& obj)
{
obj.shouData();
}
// 2
template <typename TT1, typename TT2>
void test_Func_2(MyData<TT1, TT2>& obj)
{
obj.shouData();
}
// 3
template <class T>
void test_Func_3(T& obj)
{
obj.shouData();
}
5.类模板和继承
template <class F_Type>
class Father
{
public:
F_Type m_F_val;
};
class Son : public Father<int>
{
public:
int m_S_Val;
}
注意事项:
(1)因为给对象类型,故一开始链接不到
(2)模板声明定义实现需要写在一个文件类,后缀用.hpp
6.模板与友元
即在模板中实现友元函数
若可直接在类中实现就写入类中实现;
若在类中声明,在类外实现需要首先在声明的()前添加一个<>;其次注意声明顺序,让变量互相认识
八、知识点补充
1.c++异常
程序执行期间出现问题
/*
throw 抛出异常
try 尝试有异常的代码
catch 接受前面抛出的异常并解决
try
{
//...
直接或间接有throw
不触发throw,代码正常向下运行;
触发throw到catch,发生中断
//...
}
catch(接受异常)
{
处理;
}
*/
if(b == 0)
{
// 有异常情况 -> 打报告
// throw抛出异常信息(支持多种类型)
throw "这里有问题";
// throw 666;
// throw 's';
// throw 3.14;
}
// 如果抛出异常后面语句不执行
int main()
{
try
{
cout<<test(9,0)<<endl;
}
catch(const char* str)
{
cout<<str<<endl;
}
catch(int num) // 是哪个类型调用哪个catch,(...)其余类型,用法类似case里面的default
{
cout<<num<<endl;
}
catch(...)
{
cout<<"不对劲"<<endl;
}
return 0;
}
2.使用自定义异常
#include
exception 为共同父类,利用其去定义自定义类
3.文件流
(1) 流的概念
数据无结构化传递(抽象)
iostream - 输入输出流
(2) fstream的使用
file stream 即文件流 – 操作文件
(3) 常见的成员函数
#include<fstream>
int main()
{
fstream obj;
obj.open("文件路径", ios::in); // ("文件路径", 打开方式); 可混合:ios::out | ios::trunc
obj.close();
obj.is_open(); // 判断是否成功打开
obj.eof(); // 判断是否达到文件尾
// 写
obj.open("text_1.txt", ios::out);
obj.put('s');
char ch = 'a';
obj.put(ch);
obj.close();
// 读
obj.open("text_1.txt", ios::in);
obj.get(ch);
cout<<ch<<endl;
obj.close();
}
// 利用二进制读写
obj.open("text_2.txt", ios::out);
int num = 99;
obj.write((const char*)& num, sizeof(int));
obj.close();
obj.open("text_2.txt", ios::in);
int val = 0;
obj.read((char*)& val, sizeof(int));
obj.close();
cout<<"val = "<<val<<endl;
4.使用重载输入输出
<< >> 直接按照流操作,中文占两个字符
obj.open("text_3.txt", ios::out);
obj<<"勇敢牛牛,不怕困难!";
obj<<"123"<<endl;
obj.close;
char str[64];
int num = 0;
obj.open("text_3.txt", ios::in);
obj>>str;
obj>>num;
cout<<str<<endl;
cout<<"num = "<<num<<endl;
obj.close;
5.c++11标准
(1) 初始化
int a = 0;
int b(2.1);
int m{4};
int n = {(int)3.14};
(2) 指针变量
int* p = NULL; // 宏定义
int* p = nullptr; // 关键字
(3) 自动类型auto
可自动推断,主要适用于明确类型,但懒得写
auto val(3.14) // 此时auto 类型为double
auto val(3) // 此时auto 类型为int
(4) decltype() 复制类型
int a = 0;
decltype(a) b; // 根据a的类型定义一个和a类型一致的b变量
decltype((a))m = a; // 给a取别名m
(5) 新的for规则
for(auto ch: str)
{
cout<<ch<<" "; // 使用ch便利str
}
(6) 给类型取别名
typedef int INT;
// 等价于
using INT = int;
typedef void(*PFun)();
// 等价于
using PFUN = void(*)();
(7) default 在类中用法
class CA
{
public:
CA();
}
CA::CA()= default; // 默认调用默认构造
(8) final
class CA
{
public:
virtual void fun() final{};
}