本文的参考代码见:https://download.csdn.net/download/qq_34014247/12698307
基础知识
- system(“pause”);//阻塞功能
- :: 的作用是:扩展作用域,通常变量满足就近原则,变量前加上 :: 可以访问全局变量(如果有的情况下)
例如:
int stk = 200;//在外面定义为全局变量
void test01()
{
int stk = 100;
//变量的作用域的原则为就近原则
cout << "攻击力=" << stk;
//双冒号 作用域运算符 ::全局作用符
cout << "全局攻击力=" << ::stk;//c++中的扩展
}
- namespace:用于解决命名冲突的问题
1.命名空间下可以放函数,变量,结构体,类,例如:
namespace A
{
void func();//函数
int m_A=20;//变量
struct person//结构体
{
};
class Animal {//类
};
//嵌套命名空间
namespace B
{
int m_A = 10;//需要重新定义
}
}
//命名空间是开发的,可以随机向其内部添加数据
namespace A//此A命名空间回和上面的命名空间A经行合并
{
int m_B = 1000;
}
2.命名空间必须定义在全局作用域下(不能写在函数的内部)
3.命名空间可以嵌套命名空间
4.命名空间是开放的,可以随时往原先的命名空间中添加内容
5.无名或者匿名命名空间,只能在当前文件内使用
namespace//当写了无名命名空间,相当于写了static int m_C;static int m_D;
{ //静态变量:只能在当前文件内使用
int m_C = 0;
int m_D = 0;
}
6.命名空间可以起别名
//命名空间可以起别名
namespace shuchu = std;//起别名
shuchu::cout << "输出" << endl;
- using声明和编译
void test()
{
int sunwukongId = 20;
// cout << "sunwukongId=" << sunwukongId <<endl;//就近原则
//using 声明,注意编码二义性问题
//写了using声明后,下面这行代码说明以后看到的sunwukongId是用KingGlory下的
//但是,编译器又有就近原则
//二义性
// using KingGlory::sunwukongId;//会报错
cout << "sunwukongId=" << sunwukongId << endl;//以后都使用就近原则
//或者加上命名空间,指定调用哪个参数,减少二义性
using namespace KingGlory;//打开房间,就近原则更强,(上述代码不屏蔽)
cout << KingGlory::sunwukongId << endl;
}
- const高级
//1.const分配内存,取地址会分配零时内存
//2.extern编译器也会给const分配变量内存(提高作用域)
void test01()
{
const int m_A = 10;
int *p = (int *)&m_A;//会分配零时内存(一定要强转到int * 类型)
}
可修改的const,通过变量赋值的方式
//3.用变量初始化const变量
void test02()
{
int a = 10;
const int b = a;//会分配内存(如果用数字赋值则无法修改)
int *p = (int *) &b;
*p = 1000;
cout << "b=" << b << endl;
}
自定义的数据类型
//4.自定义的数据类型
struct Person {
string m_Name;
int m_Age;
};
void test03()
{
const Person p1;
Person *p = (Person *)&p1;//(只要分配了内存都可以通过指针来修改内部的数据值)
p->m_Name = "德玛西亚";
(*p).m_Age = 19;
cout << "姓名=" << p1.m_Name << "年龄=" << p1.m_Age << endl;
}
引用的语法
1.引用基本语法 Type &别名 = 原名
2.引用必须初始化
//int &a; 必须初始化(光秃秃的则会报错)
int a = 10;
int &b = a; //引用初始化后不可以修改了
int c = 20;
b = c; //赋值!!!
3.对数组的引用
方式1: 对每个数组元素都起引用: int(&pArr)[10] = arr;//起别名
==方式2:typedef int(ARRAYREF)[10];//一个具有10个元素的int类型的数组
ARRAYREF & pArr2 = arr;//将arr取别名为pArr2 ==
void test03()
{
int arr[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i;
}
//1.方式1给数组起别名
int(&pArr)[10] = arr;//起别名
for (int i = 0; i < 10; i++)
{
cout << pArr[i] << " ";
}
cout << endl;
//2.方式2给数组起别名
typedef int(ARRAYREF)[10];//一个具有10个元素的int类型的数组
ARRAYREF & pArr2 = arr;//将arr取别名为pArr2
for (int i = 0; i < 10; i++)
{
cout << pArr2[i] << " ";
}
cout << endl;
}
- 参数的传递方式(实现)
1.值传递void mySwap(int a, int b)
2.地址传递void mySwap02(int *a, int *b)
3.引用传递void mySwap03(int &a, int &b)
引用的注意事项
1.引用必须引一块合法的内存空间
2.不要返回局部变量的引用
指针的引用
可以用来开辟空间
例如:
struct Person {
int m_Age;
};
//方法1,通过指针分配内存
void allocMemory(Person **p)//**p具体的Person对象 *p对象的指针 p指针的指针
{
*p = (Person *)malloc(sizeof(Person));
(*p)->m_Age = 100;
}
//方法2,通过指针的引用分配内存
void allocMemoryRef(Person* &p)//**p具体的Person对象 *p对象的指针 p指针的指针
{
p = (Person *)malloc(sizeof(Person));
(p)->m_Age = 1100;
}
//实例化:
Person *p1=null;//Person的指针为空
//分配内存空间
allocMemoryRef(p)
//或者可使用allocMemory(&p);
常量引用
void test01()
{
// int &ref = 10;//引用不合法的内存,不可以
const int &ref = 10;//加入const后,编译器处理方式为:int temp=10,const int &ref=temp;
//ref=10;
int *p = (int *)&ref;//尽量不要修改别人的代码,虽然可以修改。
*p = 1000;
cout << "ref=" << ref << endl;
}
//常量引用的使用场景,用来修饰形参
void showValue(const int &val)
{
// val += 1000;//如果只是想显示内容,而不修改内容,那么就用const修饰这个形参
cout << "val=" << val << endl;
}
内联函数(只要解决的是效率的问题)
Tip: 只有当函数只有 10 行甚至更少时才将其定义为内联函数.
定义: 当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.
优点: 当函数体比较小的时候, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.
缺点: 滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
- 宏缺陷#define:
- 宏定义是完全的替换 #define MyAdd(x,y) (x)+(y)
- 宏函数没有作用域
内敛函数的主要事项
内联函数:类内部的(成员函数) 默认前面会加inline关键字
内联函数是以空间换时间,函数的替换
inline void func(); //内联函数声明
inline void func() {//如果函数实现时候,没有加inline关键字 ,那么这个函数依然不算内联函数
};
函数的默认参数和占位符
默认参数,是参数后有“="…
如果有一个位置有了默认参数,那么从这个位置开始,从左往后都必须有默认参数
函数声明和实现里 只能有一个里有默认参数,不要同时都出现默认参数(切记,不然会报:重定义的错误)
void myFunc(int a = 10, int b = 10);
void myFunc(int a, int b) {
}
占位符
//函数 占位参数(注意:c语言没有默认参数也没有占位参数,只有c++中含有有)
//如果有了占位参数,函数调用时候必须要提供这个参数 ,但是用不到参数
//占位参数 没有什么大用途,只有后面重载 ++符号才有一点点用
//占位参数 可以有默认值
void func2(int a, int = 1)
{
}
函数的重载
函数的重载
- 必须在同一个作用域中,(例如,同一个文件,同一个类中等)
- 函数的参数个数不同,参数类型不同,或者参数的顺序不同
- const也可以作为重载的条件
- 函数的返回值不同,不能作为重载的条件(会产生二义性的问题,编译器根据入口不知道要找到那个函数)
void func()
{
cout << "无参数的func" << endl;
}
void func(int a)
{
cout << "有参数的func" << endl;
}
void func(double a)
{
cout << "有参数的func(double a)" << endl;
}
void func(double a, int b)
{
cout << "有参数的func(double a ,int b)" << endl;
}
void func(int a, double b)
{
cout << "有参数的func(int a ,double b)" << endl;
}
/**************************************************/
//引用的重载版本
//const也是可以作为重载的条件
void func3(int &a)//引用必须拥有合法的空间
{
cout << "有参数的func3(int &a)" << endl;
}
void func3(const int &a)//const也是可以作为重载的条件 int temp=10;const int &a=temp
{
cout << "有参数的func3(const int &a)" << endl;
}
类与对象
c语言下的封装(结构体):属性和行为分开处理了 ,类型检测不够
c++:将属性和行为封装在一起,统一在类内
构造函数
- 建议成员变量设置成私有属性
比较两个变量的大小
//类内书写
//通过成员函数判断是否相等
bool compareCubeByClass(Cube & cub)
{
bool ret = m_L == cub.getL() && m_W == cub.getW() && m_H == cub.getH();
return ret;
}
//类外书写
//全局函数判断 两个立方体是否相等
bool compareCube(Cube &cub1, Cube & cub2)
{
if (cub1.getL() == cub2.getL() && cub1.getW() == cub2.getW() && cub1.getH() == cub2.getH())
{
return true;
}
return false;
}
构造与析构
-
构造函数
与类同名,没有返回值,不写void,可以发生重载(可以有参数)
构造函数由编译器自动调用,只会在类初始化的时候调用一次 -
析构函数
与类名相同 类名前面加一个符号 “~” ,也没有返回值 ,不写void, 不可以有参数(不能发生重载)
类回收释放内存的时候自动调用,也只会调用一次
class Person
{
public:
//构造函数写法
//与类名相同,没有返回值 ,不写void,可以发生重载 (可以有参数)
//构造函数由编译器自动调用,而不是手动,而且只会调用一次
Person()
{
cout << "构造函数调用" << endl;
}
Person(int a)
{
cout << "构造函数调用(int a)" << endl;
}
//析构函数写法
// 与类名相同 类名前面加一个符号 “~” ,也没有返回值 ,不写void, 不可以有参数(不能发生重载)
//自动调用,只会调用一次
~Person()
{
cout << "析构函数调用" << endl;
}
};
构造函数的调用
构造函数有三种:默认构造,有参构造,拷贝构造
注意:拷贝构造的时候,要加上const,保证拷贝的时候,不能改变人家的数据
//分类
//按照参数进行分类 无参构造函数(默认构造函数) 有参构造函数
//按照类型进行分类 普通构造函数 拷贝构造函数
class Person
{
public: //构造和析构必须写在public下才可以调用到;必须是公有的
Person() //默认 、 无参构造函数
{
cout << "默认构造函数调用" << endl;
}
Person(int a)
{
cout << "有参构造函数调用" << endl;
}
//拷贝构造函数(根据张三,拷贝一份数据,const保证在拷贝的时候不能改变人家的数据)
Person(const Person& p)
{
m_Age = p.m_Age;//选择要拷贝的数据
cout << "拷贝构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数调用" << endl;
}
int m_Age;
};
- 构造函数的调用方法
//括号调用法(以上方的类为例子)
Person p1;//默认构造
Person P2(10);//有参构造
Person P3(P1);//拷贝构造
//显示调用方法
//显示法调用
Person p4 = Person(100);
Person p5 = Person(p4);
//隐式调用方法
Person p7 = 100; //相当于调用了 Person p7 = Person(100) ,隐式类型转换
Person p8 = p7; // 相当于 Person p8 = Person(p7);
//匿名对象调用方法
Person(100);//叫匿名对象 ,匿名对象特点,如果编译器发现了对象是匿名的,那么在这行代码结束后就释放这个对象
//1不能用拷贝构造函数来初始化匿名对象
//Person(p4); //2如果写成左值,编译器认为你写成 Person p5;
Person p6 = Person(p5); //3对象的声明,如果写成右值,那么可以
构造函数的调用规则
-系统默认给一个类提供 3个函数 默认构造 、 拷贝构造 、 析构函数
- 当我们提供了有参构造函数,那么系统就不会在给我们提供默认构造函数了(你写了,系统就不给你提供构造函数),但是 系统还会提供默认拷贝构造函数 , 进行简单的值拷贝
- 当我们提供了 拷贝构造,那么系统就不会提供其他构造了
深拷贝与浅拷贝
拷贝构造 系统会提供默认构造拷贝,而且是简单的值拷贝(不写拷贝函数,使用系统自己定义的拷贝构造函数)
初始化列表
构造函数后面加上: 属性(参数), 属性(参数)…
class Person
{
public:
//Person()
//{
//}
//有参构造,初始化数据
/*Person(int a,int b,int c)
{
m_A=a;
m_B=b;
m_C=c;
}*/
//利用初始化列表 初始化数据
// 构造函数后面加上: 属性(参数), 属性(参数)...
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
{
}
//默认的初始化列表写法:(值写死了)
Person() :m_A(10), m_B(20), m_C(30)
{}
int m_A;
int m_B;
int m_C;
};
explicit关键字:防止隐式转换
class MyString
{
public:
MyString(const char * str)
{
}
explicit MyString(int a)// explicit关键字 ,防止隐式类型转换
{
mSize = a;
}
char* mStr;
int mSize;
};
/*
MyString str2(10);
//MyString str3 = 10; //做什么用图? str2字符串为 "10" 字符串的长度10
//隐式类型转换 Mystring str3 = Mystring (10);
// explicit关键字 ,防止隐式类型转换
*/
new关键字的使用
class Person
{
public:
Person()
{
cout << "默认构造的调用" << endl;
}
Person(int a)
{
cout << "有参构造的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
1.new出来的对象,会在堆区开辟空间,需要用delete来回收内存
void test01()
{
// Person p1;//栈区开辟
Person* p1 = new Person;//堆区开辟,没有释放
//所有new出来的对象,都会返回该类型的指针
//malloc返回的是void*,还有强转
//malloc不对调用构造,new会调用构造
//new运算符,malloc函数
//释放堆区空间
//delete也是运算符,配合new用,malloc配合free用
delete p1;
}
2.不要用void* 来接收new出来的对象
void test02()
{
void *p2 = new Person;//一般不这样写,
//当用void*接受new出来的指针,会出现释放的问题
delete p2;
//无法释p2,所以要避免这种写法
//不会调用析构函数
}
3.通过new开辟数组 一定会调用默认构造函数,所以一定要提供默认构造(一定要有默认构造函数)
void test03()
{
//通过new开辟数组 一定会调用默认构造函数,所以一定要提供默认构造(一定要有默认构造函数)
//堆区开辟
Person *pArray = new Person[1];
//释放数组 delete []
delete[] pArray;
//delete pArray;//不行
//栈区开辟
Person pArray2[2] = { Person(1), Person(2) }; //在栈上开辟数组,可以指定有参构造
}
静态成员变量和方法
-
静态成员变量(多个对象共同使用的变量)
加入static就是 静态成员变量 ,会共享数据,静态成员变量,在类内声明,类外进行初始化(不能在类内进行赋值操作) -
静态成员函数(用于调用静态成员变量)
不可以访问 (普通成员变量)
可以访问 (静态成员变量)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
//m_Age = 10;
}
static int m_Age; //加入static就是 静态成员变量 ,会共享数据
//静态成员变量,在类内声明,类外进行初始化(不能在类内进行赋值操作)
// int Person::m_Age = 0; //类外初始化实现
//静态成员变量 也是有权限的
int m_A;
//静态成员函数
//不可以访问 (普通成员变量)
//可以访问 (静态成员变量)
static void func()
{
//m_A = 10;
m_Age = 100;
cout << "func调用" << endl;
};
//普通成员函数 可以访问普通成员变量,也可以访问静态成员变量
void myFunc()
{
m_A = 100;
m_Age = 100;
}
private:
static int m_other; //私有权限 在类外不能访问
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_Age = 0; //类外初始化实现(一定要类外初始化)
int Person::m_other = 50;//类外不能访问,但可以在类外进行初始化,因为有作用域,实质还是在类内操作
void test01()
{
//1 通过对象访问属性
Person p1;
p1.m_Age = 10;
Person p2;
p2.m_Age = 20;
cout << "p1 = " << p1.m_Age << endl; //10 或者 20? 20
cout << "p2 = " << p2.m_Age << endl; //20
//共享数据
//2 通过类名访问属性
cout << "通过类名访问Age" << Person::m_Age<< endl;
//cout << "other = " << Person::m_other << endl; //私有权限在类外无法访问
//静态成员函数调用
p1.func();
p2.func();
Person::func();
//静态成员函数 也是有权限的
//Person::func2();
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
注意:成员变量和成员方法是分开存储的
this指针
指向当前对象的指针
1.this可以解决命名冲突
//构造函数
Person(int age)
{
//age = age;//没法区分
this->age = age;
}
2.this指向对象的本体(this指向当前的对象,加上则指向当前的本体)
Person& PlusAge(Person &p)
{
this->age += p.age;
cout << "相加后的年龄" << this->age << endl;
return *this;//*this指向对象的本体(this指向当前的对象,加上*则指向当前的本体)
}
const常函数
class Person
{
public:
Person()
{
//构造中修改属性
//this 永远执行本体
//Person * const this(const 不能修改指针的指向)
this->m_A = 0;
this->m_B = 0;
}
void showInfo() const //常函数 不允许修改指针指向的值
{
//this->m_A = 1000;
this->m_B = 1000;
// const Person * const this
cout << "m_A = " << this->m_A << endl;
cout << "m_B = " << this->m_B << endl;
}
void show2() const
{
//m_A = 100;
}
int m_A;
mutable int m_B; //就算是常函数 我还是执意要修改
};
void test01()
{
Person p1;
p1.showInfo();
//常对象,不允许修改属性
const Person p2;
//p2.m_A = 100;//报错
cout << p2.m_A << endl;
p2.show2();//常对象不可以调用普通的成员函数
//常对象,可以调用常函数
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
友元函数
全局函数作为友元函数
将该全局函数放在类的内部进行声明,声明函数前面要加上friend友元关键字
class Building
{
//让好基友类 作为 Building的好朋友 (友元函数,在函数的声明前加上friend)
friend void goodGay(Building *building);
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
//全局函数,好基友
void goodGay(Building *building)
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
2.类作为友元类
与上方法类似,类的声明前加上friend关键字
class Building
{
//让好基友类 作为 Building的好朋友
friend class goodGay;//(类的友元类)(frind+类的声明)
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
3.成员函数做友元函数
friend 作用域::函数
//只让 visit可以作Building的好朋友 visit2 不可以访问私有属性
class Building;//告诉编译器,这个类存在,你先用着
class goodGay
{
public:
goodGay();
void visit();
void visit2();
private:
Building * building;
};
class Building
{
//让成员函数 visit作为友元函数
friend void goodGay::visit();//frieng 函数的声明(注意成员函数一定要加上作用域)
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()//友元函数可以访问
{
cout << "好基友正在访问: " << this->building->m_SittingRoom << endl;
cout << "好基友正在访问: " << this->building->m_BedRoom << endl;
}
void goodGay::visit2()//非友元函数不可访问
{
cout << "好基友正在访问: " << this->building->m_SittingRoom << endl;
//cout << "好基友正在访问: " << this->building->m_BedRoom << endl;
}
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
void test01()
{
goodGay gg;
gg.visit();
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
重载
四则运算的重载
1.类内部
class Person
{
public:
Person() { }
Person(int a, int b) :m_A(a), m_B(b)
{
}
//+号运算符重载,成员函数
//Person operator+(Person &p1)
//{
// Person tmp;
// tmp.m_A = this->m_A + p1.m_A;
// tmp.m_B = this->m_B + p1.m_B;
// return tmp;
//}
int m_A;
int m_B;
};
2.全局函数的重载(要声明为类的友元函数)
//利用全局函数进行运算符的重载
Person operator+(Person &p1, Person &p2)//两个对象的重载
{
Person tmp;
tmp.m_A = p2.m_A + p1.m_A;
tmp.m_B = p2.m_B + p1.m_B;
return tmp;
}
左移运算符的重载
- 重载左移运算符不可以写到成员函数中
- 所有在定义的时候要声明为类的友元函数
ostream& operator<<(ostream&cout,Person& p1)//第一个参数是cout,第二个参数是p1
{
//cout的逻辑函数
cout << "m_A = " << p1.m_A << " m_B = " << p1.m_B;
return cout;
}
自增和自减运算符
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger & myInt);//友元函数
public:
MyInteger()
{
m_Num = 0;
}
//前置++重载
MyInteger& operator++()
{
//先++,在返回值
this->m_Num++;
return *this;//返回自己的实体
}
//后置++ 重载
MyInteger operator++(int)//占位参数,(只用于后置++的重载)
{
//先保存目前数据
//在++
//在返回值
MyInteger tmp = *this;
m_Num++;
return tmp;//相当于返回本体(将旧值返回回去)
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInteger & myInt)//重载左移运算符
{
cout << myInt.m_Num;
return cout;
}
void test01()
{
MyInteger myInt;
++myInt;//前置++
cout << ++myInt<< endl;
MyInteger myInt1;
myInt1 = (myInt++);
cout << myInt1 << endl;
}
指针运算符的重载
智能指针
class Person
{
public:
Person(int age)
{
this->m_Age = age;
}
void showAge()
{
cout << "年龄为:" << this->m_Age << endl;
}
~Person()
{
cout << "Person的析构调用" << endl;
}
int m_Age;
};
//智能指针
//用来托管自定义类型的对象的释放,让对象经行自动的释放
class smartPointer
{
public:
smartPointer(Person * person)
{
this->person = person;
}
//重载->让智能指针对象 想Person *p一样去使用
Person* operator->()
{
return this->person;
}
//重载*
Person& operator*()
{
return *this->person;
}
//析构函数
~smartPointer()
{
cout << "智能指针析构了" << endl;
if (this->person != NULL)
{
delete this->person;
this->person = NULL;
}
}
private:
Person *person;
};
void test01()
{
//自动析构
//Person p1(10);
//使用delet析构
//Person * p1 = new Person(10);
//p1->showAge();
//delete p1;
smartPointer sp(new Person(10));sp开辟到了栈上,自动释放(以后就不用delete p1了,自动释放)
sp->showAge(); // sp->->showAge(); 编译器优化了 写法
(*sp).showAge();//智能指针
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
赋值运算符的重载
//一个类默认创建 默认构造、析构、拷贝构造 operator=赋值运算符 进行简单的值传递
class Person
{
public:
Person(int a)
{
this->m_A = a;
}
int m_A;
};
void test01()
{
Person p1(10);
Person p2(0);
p2 = p1;//赋值
cout << "p2的m_A:" << p2.m_A << endl;
}
class Person2
{
public:
Person2(const char *name)
{
this->pName = new char[strlen(name) + 1];
strcpy(this->pName, name);
}
//重载 = 赋值运算符
Person2& operator= (const Person2 & p)//加上const不能修改
{
//判断如果原来已经堆区有内容,先释放
if (this->pName != NULL)
{
delete[] this->pName;
this->pName = NULL;
}
this->pName = new char[strlen(p.pName) + 1];//开辟内存
strcpy(this->pName, p.pName);
return *this;
}
~Person2()
{
if (this->pName != NULL)
{
delete[] this->pName;
this->pName = NULL;
}
}
char* pName;
};
void test02()
{
Person2 p1("狗大");
Person2 p2("狗小");
Person2 p3("");
p3 = p2 = p1;
cout << p2.pName << endl;
cout << p3.pName << endl;
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
关系运算符的重载
与加法的重载很类似,例如(类内)
//==重载
bool operator==(Perosn &p1)
{
if (this->m_Age == p1.m_Age && this->m_Name==p1.m_Name)
return true;
else
{
return false;
}
}
//!=重载
bool operator!=(Perosn &p1)
{
if (this->m_Age == p1.m_Age && this->m_Name == p1.m_Name)
return false;
return true;
}
函数调用运算符的重载()
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include <string>
//()重载
class MyPrint
{
public:
void operator()(string text)
{
cout <<text<< endl;
}
};
void test01()
{
MyPrint myPrint;
//重载类似于函数的调用
myPrint("hello world");//仿函数
}
class MyAdd
{
public:
int operator()(int v1,int v2)
{
return v1 + v2;
}
};
void test02()
{
//MyAdd myAdd;
//cout << myAdd(1, 2) << endl;
cout << MyAdd()(1, 2) << endl;//匿名对象(无名的对象)
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
继承
继承 减少代码重复内容
继承的方式
-
公有继承
基类中私有的属性 不可继承
基类中公有的属性 可继承,还是public
基类中保护的属性 可继承,还是protected 类外访问不到 -
保护继承
基类中私有的属性 不可继承
基类中公有的属性 可继承,还是protected
基类中保护的属性 可继承,还是protected -
私有继承
基类中私有的属性 不可继承
基类中公有的属性 可继承,还是private
基类中保护的属性 可继承,还是private
继承中的同名处理
加上作用域,否则按照就近原则执行
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base func调用" << endl;
}
void func(int a)
{
cout << "Base func (int a)调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "子类的func()调用" << endl;
}
int m_A;
};
void test01()
{
Son s1;
cout << s1.m_A << endl;//就近原则
//想调用 父类中的m_A
cout << s1.Base::m_A << endl;//对象.父类的作用域,父类中继承的同名值
s1.func();//调用子类的func
s1.Base::func();//调用父类的func
如果子类与父类的成员函数名称相同,子类会把父类的所有的同名版本都隐藏掉
//s1.func(1);//报错(子类有与父类同名的函数,父类的会被隐藏起来)
s1.Base::func(1);//正常编译通过
}
//如果子类和父类拥有同名的函数 属性 ,子类会覆盖父类的成员吗? 不会
//如果子类与父类的成员函数名称相同,子类会把父类的所有的同名版本都隐藏掉
//想调用父类的方法,必须加作用域
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
继承中的静态成员
子类可以继承父类中的静态方法
class Base
{
public:
static void func()
{
cout << "base fun()" << endl;
}
static void func(int a)
{
cout << "base fun(int)" << endl;
}
static int m_A;
};
//静态变量:类内声明,类外使用
int Base::m_A = 10;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "son fun()" << endl;
}
};
int Son::m_A = 20;
//静态成员属性 子类可以继承下来
void test01()
{
Son s1;
cout<<Son::m_A<<endl;
//访问父类的m_A
cout << Base::m_A << endl;
Son::func();
//访问 父类中同名的函数
Son::Base::func(1);
Son::Base::func();
}
多继承
常见->菱形继承
多继承很容易产生二义性加上作用域
class Base1
{
public:
Base1()
{
m_A = 10;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 20;
}
int m_A;
};
class Son :public Base1, public Base2
{
public:
int m_C;
int m_D;
};
//多继承中,很容易引发二义性
void test01()
{
cout << sizeof(Son) << endl;//全部继承
Son s1;
//s1.m_A;会产生二义性
cout << s1.Base1::m_A << endl;
cout << s1.Base2::m_A << endl;
}
多态
重点:什么是多态? 父类的引用或指针 指向 子类对象
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数
静态联编与动态联编
静态联编:早绑定
//定义一个动物类
class Animal
{
public:
virtual void speak()//加入virtual则是变成虚函数,动态联编
{
cout << "动物在说话" << endl;
}
virtual void eat()
{
cout << "动物吃东西" << endl;
}
};
//定义一个猫类
class Cat :public Animal
{
public:
virtual void speak()//在子类中virtual,可写可不写
{
cout << "小猫在说话" << endl;
}
virtual void eat()
{
cout << "小猫在吃鱼" << endl;
}
};
//调用doSpeak,speak函数的地址早就绑定好了,早绑定,静态联编,编译阶段就以确认地址
//如果想要调用猫的speak,就不能提前绑定好函数的地址,所以需要在运行时再去绑定函数的地址---->动态联编
//动态联编,将doSpeak该成虚函数的形式,在父类声明虚函数,发生多态
/*************重点:什么是多态? 父类的引用或指针 指向 子类对象 ***********************/
void doSpeak(Animal &animal)//引用 Animal & animal=cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许经行类型的转换
void test01()
{
Cat cat;
doSpeak(cat);
}
虚函数与纯虚函数
虚函数是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,(即:不需要早绑定)。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。(纯虚函数只用来继承使用)
virtual int area() = 0;告诉编译器,该函数没有主体,是纯虚函数
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;//=0告诉编译器,该函数没有主体,是纯虚函数
};
多态案例
//重点:利用多态实现计算器
class abstractCalculator
{
public:
//虚函数
//virtual int getResult() { return 0;}
//纯虚函数
//纯虚函数不需要做实现
//如果父类中有了 纯虚函数 子类继承父类 就必须要实现 纯虚函数
//如果父类中有了 纯虚函数 ,这个父类 就无法实例化对象
//如果这个类有了纯虚函数,通常称为抽象类 (抽象类无法实例化对象)
virtual int getResult() = 0;
void setV1(int v1)
{
val1 = v1;
}
void setV2(int v2)
{
val2 = v2;
}
int val1;
int val2;
};
//加法计算器
class PlusCalculator :public abstractCalculator
{
public:
virtual int getResult()
{
return val1 + val2;
}
};
//减法计算器
class SubCalculator:public abstractCalculator
{
public:
virtual int getResult()
{
return val1 - val2;
}
};
//乘法计算器
class ChengCalculator:public abstractCalculator
{
public:
virtual int getResult()
{
return val1 * val2;
}
};
class A :public abstractCalculator
{
//子类中必须要实现父类中的纯虚函数
virtual int getResult()
{
return 0;
}
};
void test02()
{
abstractCalculator * abc;
//加法计算器
abc = new PlusCalculator;
abc->setV1(10);
abc->setV2(22);
cout << abc->getResult() << endl;
//删除指针,重新赋值
delete abc;
//减法计算器
abc = new SubCalculator;
abc->setV1(10);
abc->setV2(22);
cout << abc->getResult() << endl;
//删除指针,重新赋值
delete abc;
//减法计算器
abc = new ChengCalculator;
abc->setV1(10);
abc->setV2(22);
cout << abc->getResult() << endl;
A a;
//如果父类中有了 纯虚函数 ,这个父类 就无法实例化对象
//abstractCalculator abc1 = new abstractCalculator;
//abstractCalculator abc2;
}
类型转换
1.静态类型转换
关键语法:static_cast<数据类型>(要转变量名)
基础类型可以转换
重点
- 将基类转换成子类,向下类型转换,不安全
- 将子类转换陈基类,向上类型转换,安全
- 转other类型 转换无效,必须要有父子关系才能转换
2.动态类型转换(要求严格)
dynamic_cast非常严格,失去精度不安全都不可以转换
基础类型不可以转换
3.常量类型转换
const_cast
void test05()
{
const int * p = NULL;
//取出const
int * newp = const_cast<int *>(p);
int * p2 = NULL;
//强制加上const
const int * newP2 = const_cast<const int *>(p2);
//不能对非指针 或 非引用的 变量进行转换
//const int a = 10;
//int b = const_cast<int>(a);
//引用
int num = 10;
int &numRef = num;
const int &numRef1 = const_cast<const int&>(numRef);
}
异常
1.在可能产生异常的地方,抛出异常throw 异常
//自己定义的异常类
class myException //自定义异常类
{
public:
void printError()
{
cout << "自定义的异常" << endl;
}
};
class Person
{
public:
Person()
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
};
int myDevide(int a, int b)
{
if (b == 0)
{
//如果b是异常 抛出异常
//return -1;
//throw 1; //抛出int类型异常
//throw 3.14; //抛出double类型异常 异常必须处理,如果不处理 就挂掉
//throw 'a';
//栈解旋
//从try开始 到 throw 抛出异常之前 所有栈上的对象 都会被释放 这个过程称为栈解旋
//(所有的对象都会被释放:p1和p2都会调用析构函数)
//构造和析构顺序相反
Person p1;
Person p2;
throw myException();//自己定义的异常,不愿意起名:匿名对象
}
return a / b;
}
2.接收异常(如果异常没被处理会一直向上抛出异常,直到处理为止)
void test01()
{
int a = 10;
int b = 0;
//int ret = myDevide(a, b); //早期如果返回-1 无法区分到底是结果还是异常
//c++中的异常处理
try//试一试
{
myDevide(a, b);
}
catch (int)//捕获异常
{
cout << "int类型异常捕获" << endl;
}
catch (double)
{
//如果不想处理这个异常 ,可以继续向上抛出
throw;
cout << "double类型异常捕获" << endl;
}
//catch (MyException e)//拷贝构造:匿名对象死之前,拷贝了一份数据给e用
catch (MyException *e) //MyException e,会多开销一份数据
{ //MyException &e 与上述的功能类似(最推荐使用)
//e->printError();(如果:throw &MyException();)
//e->printError();
//e->printError(); //指向非法内存空间,不应该这么做
cout << "捕获异常" << endl;
delete e; //靠自觉 释放对象
}
catch (...)
{
cout << "其他类型异常捕获" << endl;
}
}
int main()
{
try
{
test01();
}
catch (char)//如果异常都没有处理,那么成员terminate函数,使程序中断
{
cout << "main函数中的double类型异常捕获" << endl;
}
catch (...)
{
cout << "main函数中的其他类型的异常捕获" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
异常的多态使用
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//异常基类
class BaseException
{
public:
virtual void printError()
{
}
};
class NullPointerException :public BaseException
{
public:
virtual void printError()
{
cout << "空指针异常" << endl;
}
};
class OutofPointerException :public BaseException
{
public:
virtual void printError()
{
cout << "越界异常" << endl;
}
};
void doWork()
{
throw NullPointerException();
//throw OutofPointerException();
}
void test01()
{
try
{
doWork();
}
catch (BaseException &e)//多态,父类指向子类的对象,自动调用子类的方法
{
e.printError();
}
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
标准的异常
系统提供标准异常类,要包含头文件
#include
{
public:
Person(string name, int age)
{
this->m_Name = name;
//年龄做检测
if (age < 0 || age > 200)
{
//抛出越界异常
//throw out_of_range("年龄越界");
throw length_error("长度越界");
}
}
string m_Name;
int m_Age;
};
void test01()
{
try
{
Person p("张三",300);
}
catch (out_of_range &e)
{
cout << e.what() << endl;
}
catch (length_error &e)
{
cout << e.what() << endl;
}
}
标准输入输出
1.标准输入
cin.get() //一次只能读取一个字符
cin.get(一个参数) //读一个字符
cin.get(两个参数) //可以读字符串
cin.getline()
cin.ignore()//没有参数 代表忽略一个字符 ,带参数N,代表忽略N个字符
cin.peek()//偷窥,例如输入as 偷看一眼 a,然后再放回缓冲区 缓冲区中还是as
cin.putback()//放回
2.标准输出
cout.put() //向缓冲区写字符
cout.write() //从buffer中写num个字节到当前输出流中。
void test01()
{
//cout.put('c')
cout.put('c').put('d');
char buf[1024] = "hello world";
cout.write(buf,strlen(buf));
}
void test02()
{
//通过流成员函数
int number = 99;
cout.width(20);
cout.fill('*');
cout.setf(ios::left); //设置格式 输入内容做对齐
cout.unsetf(ios::dec); //卸载十进制
cout.setf(ios::hex); //安装16进制
cout.setf(ios::showbase); // 强制输出整数基数 0 0x
cout.unsetf(ios::hex);//卸载十六进制
cout.setf(ios::oct);//安装8进制
cout << number << endl;
}
//控制符的方式显示
void test03() {
int number = 99;
cout << setw(20)
<< setfill('~')
<< setiosflags(ios::showbase) //基数 0x
<< setiosflags(ios::left) //左对齐
<< hex // 十六进制
<< number
<< endl;
}
文件读写
1.写入文件
- 创建ofstream流
- 判断文件是否已经打开
- 写入内容
- 关闭文件
//写文件
void test01()
{
//以输出的方式打开文件
//ofstream ofs("./test.txt", ios::out | ios::trunc);
//后期指定打开方式
ofstream ofs;
ofs.open("./test.txt", ios::out | ios::trunc);
//是否打开成功
if (!ofs.is_open())
{
cout << "打开失败" << endl;
}
//打开成功后输入值
ofs << "姓名:abc" << endl;
ofs << "年龄:100" << endl;
ofs << "性别:男" << endl;
ofs.close();//关闭文件
}
2.读取文件
//读文件
void test02()
{
ifstream ifs;
ifs.open("./test.txt", ios::in);
//判断是否打开成功
if (!ifs.is_open())
{
cout << "打开失败" << endl;
}
//第一种方式(推荐)
//char buf[1024];
//while (ifs>>buf)//按行读取
//{
// cout << buf << endl;
//}
//第二种方式
//char buf2[1024];
//while (!ifs.eof())//eof读到文件尾
//{
// ifs.getline(buf2, sizeof(buf2));
// cout << buf2 << endl;
//}
//第三种方式 不推荐 按单个字符读取
char c;
while ((c = ifs.get()) != EOF) // EOF文件尾
{
cout << c;
}
ifs.close();//关闭文件
}