C++基础知识总结(详解)

news2025/3/13 0:33:21

前言

文章篇幅较大,内容充实
请在阅读的过程中保持一个批判的态度

C++基础

C++头文件

C++有自己的头文件,例如iostream 没有.h
也可以用C的头文件 例如stdio.h 有.h
或者将C文件C++化 例如cstdio 前面加c没有.h

名字空间

划分逻辑单元。避免名字冲突

创建名字空间

::作用域限定符

表示::后面的内容属于::前面。翻译成中文就是 的
如果::前面没有内容表示全局


使用:

名字空间合并

有时候一个名字空间太冗余或者其他原因需要分开实现在不同地方

这里不是覆盖而是合并,将同名名字空间合并在一起

声明和定义分开

名字空间嵌套


名字空间别名

名字空间指令

using namespace 名字空间名
使用后该名字空间对于当前作用域可见,可以不再使用作用域限定符,一旦使用,不可再隐藏。

std名字空间全局可见。std标准库定义名字空间

  • 若不同名字空间,需要注意二义性,因为编译器有选择困难症,此时仍然需要使用作用域限定符来明确作用域

C++结构体、联合、枚举

C++结构体

声明或定义结构体变量,可以省略struct
内部可以定义函数

C++ 联合

声明或定义联合变量,可以省略union
支持匿名联合

C++枚举

声明或定义枚举变量,可以省略enum
独立类型和整型不能隐式相互转换

Demo:实现不同银行账户存取。

  • 工商银行(ICBC)原本余额(balance) 2000元,存(save)3000,取(draw)2000之后显示(show)余额
  • 农业银行(ABC)原本余额(balance) 4000元,取(draw)1500,存(save) 800之后显示(show)余额
namespace ICBC
{
	int balance = 2000;
	void save(int money)
	{
		cout << "工商银行存入:" << money << endl;
		balance += money;
	}
	void draw(int money)
	{
		cout << "工商银行取出:" << money << endl;
		balance -= money;
	}
	void show()
	{
		cout << "工商银行余额:" << balance << endl;
	}
}
namespace ABC
{
	int balance = 4000;
	void save(int money)
	{
		cout << "农业银行存入:" << money << endl;
		balance += money;
	}
	void draw(int money)
	{
		cout << "农业银行取出:" << money << endl;
		balance -= money;
	}
	void show()
	{
		cout << "农业银行余额:" << balance << endl;
	}
}
int main(){
	ICBC::save(3000);
	ICBC::draw(2000);
	ICBC::show();
	cout << "\n";
	ABC::draw(1500);
	ABC::save(800);
	ABC::show();
	return 0;
}

bool类型

  • true表示真 单字节整数1
  • false表示假 单字节整数0

任何基本类型都可以隐式转换为布尔类型
非0即真,0即假

boolalpha bool类型使用字符输出
noboolalpha bool关闭字符输出,数值输出


内联

  • 用函数已被编译好的二进制代码,替换对该函数的调用指令。提高效率,避免函数调用开销。
  • 使用inline关键字期望该函数被优化为内联,是否内联由编译器决定
  • 内联会使可执行文件内存变大,只有频繁调用的简单函数适合内联。复杂函数和递归函数都不适合内联。

函数重载

同一作用域中,函数名相同,参数表不同的函数。不同作用域同名函数遵循标识符隐藏原则(临近隐藏原则)。
重载和返回值和参数名没有关系

调用时会根据参数类型自动选择类型一致的函数

重载和返回类型无关

函数作用域不是定义决定的,是声明决定的。

重载是编译器通过换名实现,
在linux下 用gcc -c 获取.o 使用nm .o文件查看
在windows下查看obj文件,或者不定义函数,只声明和使用
通过extern “C”可以要求C++编译器按照C方式处理函数接口

缺省参数和哑元

为函数指定缺省值,调用时若未指定实参,则对应的形参取缺省值。

缺省参数的特点

  1. 最好在函数声明中指定。可以利用声明改缺省值。
  2. 禁止在声明和定义同时指定缺省参数。可能不一致,编译器禁止
  3. 缺省参数只能在最后,即你某个参数指定为缺省参数,后面所有参数都要有缺省值
  4. 不要因为是用缺省参数导致重载歧义

只指定类型而不指定名称的函数参数,叫做哑元。
使用哑元1.兼容之前版本。二.形成函数重载

引用

引用(reference)是c++对c语言的重要扩充。
引用就是某一变量(内存)的一个别名,对引用的操作与对变量直接操作完全一样。其格式为:
类型 &引用变量名 = 已定义过的变量名。

&符号:跟在类型后是引用,没有类型是取地址
*符号:跟在类型后是指针,没有类型是解引用

诸葛亮 诸葛孔明 卧龙 蜀国丞相

引用的特点

  • 一个变量可取多个别名。
  • 引用必须初始化,不能为空。
  • 引用只能在初始化的时候引用一次 ,不能更改为转而引用其他变量。

const引用

引用变量和被引用的变量虽然是同一个变量,但是可以被不同修饰符修饰

引用做参数

  • 节省空间+提高效率
  • 值传递,形参生成局部临时变量接收实参的值。
  • 引用传递,形参是实参的别名
  • 指针传递,传入实参的地址,指针通过地址访问修改值


引用做函数返回值

当引用做函数的返回值时: 函数可以放在赋值语句的左边(可以当左值)

动态分配+内存池

使用标准C malloc/calloc/realloc/free

使用new delete

在分配内存的同时初始化

数组方式new的需要以数组方式delete

在C11标准中可以初始化数据

内存池

预先分配好,放到进程空间的内存块,用户申请与释放内存其实都是在进程内进行,遇到小对象时就是基于内存池的。只有当内存池空间不够时,才会再从系统找一块很大的内存

引用和指针的区别和联系

两者都是地址的概念
指针指向一块内存,其内容为所指内存的地址;
引用是某块儿内存的别名。

  1. 指针是一个实体,而引用仅是个别名;
  2. 引用使用时无需解引用(*),指针需要解引用;
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  4. 引用没有 const,指针有 const;const修饰的指针不可变;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
  7. 指针和引用的自增(++)运算意义不一样;
    8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

Demo: 使用引用和指针实现加法计算


Demo:new 和delete实现二维数组的动态申请内存

用这个方法来创建二维数组,比较直观、易用,但它最大的限制在于:你必须在编译时确定b的大小。

低一级的数组是分开创建的,所以整个二维数组的内存不连续——类似‘array2D[i * width + j]’这样的访问就不要使用了,容易造成访问越界。

C++面向对象

为什么使用面向对象

  • 适合开发大型软件
  • 提升效率,降低成本
  • 已有现成的设计模式和应用框架,拿来就能用

什么是对象

万物皆对象
底层对象能够从高层对象继承一些属性和行为。
例如小狗继承动物的一些属性和行为,动物继承生物的一些属性和行为。
属性和行为可以完整的描述对象。属性是状态,特征。行为是能做什么。
例如认识陌生人,先关注性别,外貌,再交谈
面向对象三大特性:继承,封装和多态

类和对象

拥有相同属性行为的一组对象统一描述成一个类
人类:男人,女人,泰国人妖 等等对象
属性:身高,体重,年龄,三围 等等
行为:吃饭,睡觉,打豆豆,么么哒 等等
类是对对象的抽象,对象是类的具体(实例)化

类的定义

访问类型限定符

  1. public 共有成员
    谁都可以访问(使用/调用)
  2. protected保护成员
    只有自己和子类可以访问
  3. private 私有成员
    只有自己(类内部)可以访问
  4. 成员访问
    访问限定符仅作用于类,因此同一个类的不同对象,可以相互访问非共有部分。
  5. 类和结构体区别
    在C++中,类和结构没有本质区别,唯一不同在于
    类的默认访问控制属性为private
    结构的默认访问控制属性为public
  6. 封装
    对不同成员的控制访问属性加以区分,体现了C++作为面向对象语言的封装特性
class People
{
public:
	char name[20];
	int  age;
	int  weight;

	void eat(char* food)
	{
		cout << "我吃了" << food << endl;
	}
	void play(char* game)
	{
		cout << "我玩了" << game << endl;
	}
};

创建对象

在栈中创建对象

  • 在栈中创建一个对象
    格式: 类名 对象名
    People people;
  • 在栈中创建多个对象
    类名 对象数组名[元素个数]

在堆中创建对象

  • 在堆中创建单个对象
    类名 *指针名 = new 类名
    People *sirius = new People;
  • 在堆中创建多个对象
    类名 *对象数组指针名 = new 类名[元素个数]

String类


Demo: 练习类的使用

狗类

有一个狗类,属性有姓名,犬龄,品种,毛色(不可以外部直接设置)。行为有进食,奔跑,睡觉。
请分别在堆区和栈区实例化一个对象。依次进食,奔跑,睡觉

调用

对象数组

创建一个对象数组,存放4个学生(学号,成绩)(学号和成绩不可外部直接设置,且设置学号时不可重复),设计一个函数max,找出这4个学生成绩最高的,并输出学号。

调用
第二种方式

在这里插入图片描述

类的构造函数

  1. 为什么使用构造函数
    变量有初始化,对于对象,也需要对成员变量赋初值,分配资源(动态内存分配),设置对象的初始状态。
  2. 什么是构造函数
    特殊的成员函数,函数名与类名相同,并且没有返回类型/返回值
  3. 构造函数用在哪里
    在创建对象时自动被调用,用户不能调用,仅被调用一次
    对象创建过程 为对象分配内存空间
    调用构造函数(基类部分,成员变量,构造代码)
  4. 构造函数怎么用
    • 默认(缺省)构造函数
      • 如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。
        例如作业中的Bank类,没有定义构造函数。
        Bank ICBC;调用了默认构造函数
  • 构造函数的重载
    和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。
    构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
    一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。

构造函数为什么要放在public
在外部实例化对象时自动调用构造函数,如果是其他访问方式,没办法调用构造函数,也就不能实例化对象。
(特例,单例模式等。)

参数初始化表

简化成员变量初始化。仅仅只是为了书写方便,没有效率上的提升


注意,参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关

析构函数

  1. 为什么使用析构函数
    创建对象时系统会自动调用构造函数进行初始化工作,对应的,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。

  2. 什么是析构函数
    析构函数也是一种特殊的成员函数,没有返回值,
    构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。

  3. 析构函数用在哪里
    在销毁对象时自动执行。不能显示调用。

  4. 析构函数怎么用
    析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。
    如果用户没有定义,编译器会自动生成一个默认的析构函数。这个析构函数的函数体是空的,也没有形参,也不执行任何操作。

this指针

this 是 C++ 中的一个关键字,也是一个 const 指针。指向当前对象,通过它可以访问当前对象的所有成员。
用->来访问成员变量或成员函数。

成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。

类中const成员

  1. const成员变量
    必须使用初始化参数列表,初始化后不能修改
  2. const成员函数
    const成员函数不能修改基本成员变量。不能调用非const成员函数
  3. const对象
    对象中的数据成员不允许被改变。
    常对象只能调用常成员函数

思考一下代码的输出,注意指针和对象

#include<iostream>
using namespace std;
class Test{
    char x;
public:
	Test(char c='A')
	{
		cout << c;
	}
};
int main() {
    Test p1, *p2; // 只实例化了p1 p2只是一个指针
    p2 = new Test('B'); 
    delete p2; 
    return 0;
}

总结

  1. 构造函数,初始化成员变量和分配资源。实例化对象的时候自动调用。
  2. 没有返回值,函数名和类名相同。
    用户不定义编译器提供无参空函数体的默认构造函数
  3. 用户可以重载构造函数。或者重新定义无参构造函数。一旦重载或者重定义,原来的默认构造函数将不提供
  4. 代码中一旦出现构造函数,默认构造函数不提供
  5. 析构函数,清理资源,对象销毁时自动调用。无参,不能重载。只可以重新实现。
    代码中一旦出现析构函数,默认析构函数不提供

拷贝构造函数(复制构造函数)

一种特殊的构造函数,从相同类型的对象构造。

同构造函数一样,如果用户不提供拷贝构造函数,编译器提供默认拷贝构造函数。
注意:因为拷贝构造函数属于构造函数,有的编译器会在用户提供构造函数的时候也不再提供拷贝构造。

默认拷贝构造函数原型

类名(const 类名& 变量名);

浅拷贝

复制地址

浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针。

深拷贝

复制内存

深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的。拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。

何时需要自定义拷贝构造

  • 是否开启新的内存地址
  • 是否影响内存地址的引用计数

如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。



该代码报错:报错原因是n1,n2,n3的m_pAge共用一块内存,delete n3的时候,内存已经释放,离开作用域调用析构时,m_pAge成为野指针。

能成功调用三次析构,且每个对象m_pAge地址不一样

类static成员

static成员变量

  • 静态成员在类的所有对象中是公有的
  • 内存是程序运行时分配
  • 使用前必须初始化且只能初始化一次
  • 初始化不能在类定义中,通过作用域限定符初始化

类型 类名::变量名=值;

- 静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。
  • 优点
    static成员的名字是在类的作用域中,因此可以避免与其它类成员或全局对象名字冲突。
    可以实施封装,static成员可以是私有的,而全局对象不可以。
    阅读程序容易看出static成员与某个类相关联,这种可见性可以清晰地反映程序员的意图。

static成员函数

  • 不再属于对象,不需要通过对象访问
  • static成员函数没有this指针,所以静态成员函数不可以访问非静态成员。只能访问静态成员(静态成员和静态变量)
  • 非静态成员函数可以访问静态成员
using namespace std;

class test2
{
public:
    test2(int num) : y(num){}
    ~test2(){}//( 构造 析构 )函数

    static void testStaticFun()//静态成员函数
    {
      cout << "y = " << y << endl; //Error:静态成员函数不能访问非静态成员
    }

    void testFun()//非静态成员函数
    {
        cout << "x = " << x << endl; 
    }
private:
    static int x;//静态成员变量的引用性说明
    int y;
};

int test2::x = 10;//静态成员变量的定义性说明

int main(void)
{
    test2 t(100);
    t.testFun();

    return 0;
}

/*
*	静态成员属于类而不属于对象,生命周期进程级
*	相当于全局变量和全局函数,只是多了类作用域和访问控制属性限制
*	静态成员依然后类的作用域和访问控制限定符约束
*	
*	静态成员变量的定义和初始化只能在类外部,不能在构造函数
*	静态成员变量为该类所有对象共享
*	访问静态成员可以通过对象也可以直接通过类
*	
*	静态成员函数没有this指针,没有常属性
*	静态成员函数只能访问静态成员  (没有this指针)
*/
class Account
{
public:
	Account(string const&name, int no, double balance) : m_name(name), m_no(no), m_balance(balance)
	{
	}

	void save(double money)
	{
		m_balance += money;
	}

	void draw(double money)
	{
		if (money > m_balance)
			cout << "余额不足" << endl;
		m_balance -= money;
	}

	void query()const
	{
		cout << "户名:" << m_name << endl;
		cout << "账号:" << m_no << endl;
		cout << "余额:" << m_balance << endl;
	}

	void settle()
	{
		this->m_balance *= (1 + m_rate / 100);
	}

	static void adjust(double rate)  
	{
		//this->m_name; 没有this 不能访问 静态成员函数只能访问静态成员
		if (rate>0)
		{
			m_rate = rate;
		}
	}

private:
	string m_name;
	int m_no;
	double m_balance;
	static double m_rate;//类里声明
};
//定义静态成员变量
/*静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义
*定义是给静态成员变量分配内存。
*只能在定义的时候初始化*/
double Account::m_rate =0.2 ;//类外定义 初始化

int main(int argc, char* argv[])
{
	Account acc1("小风同学", 101, 4000);
	acc1.draw(2000);
	acc1.query();

	Account acc2("海绵", 102, 40000);
	acc2.save(50000);
	acc2.query();
	cout << "-----------------" << endl;
	cout << sizeof(acc2) << endl;
	acc1.settle();
	acc1.query();
	acc2.settle();
	acc2.query();

	getchar();
	return 0;
}

Demo:实现单例模式

//饿汉式 程序启动就创建 不管你用不用
class single
{
private:
	int m_data;
	single(){}
	single(int data) :m_data(data){}
	single(const single&){}
	static single s_instance;

public:
	static single& getinstance()
	{
		return s_instance;
	}
};

single single::s_instance(100);


class singleton
{
private:
	int m_data;
	singleton(){}
	singleton(int data) :m_data(data){}
	singleton(const singleton&){}
	static singleton* s_instance;

public:
	static singleton& getinstance()
	{
		if (!s_instance)
		{
			s_instance = new singleton(200);
		}
		return *s_instance;
	}
};

singleton* singleton::s_instance = NULL;

int main(int argc, char* argv[])
{
	//1  隐藏所有构造函数
	//2  内部调用构造函数来实例化静态成员对象
	//2  提供一个静态函数来返回静态成员对象

	single& s1 = single::getinstance();
	single& s2 = single::getinstance();
	cout << &s1 << " " << &s2 << endl;

	singleton& st1 = singleton::getinstance();
	singleton& st2 = singleton::getinstance();
	cout << &st1 << " " << &st2 << endl;
	getchar();
}

友元(破环封装性的棒槌)

友元函数

#if 0
class Point3D;
class Point2D
{
	//友元函数
	//声明print为友元函数,可以访问Point2D隐藏
	friend void print(const Point2D& point);
	//友元类
	friend class Point3D;
public:
	Point2D(int x = 0, int y = 0) :m_x(x), m_y(y){}
	void print()
	{
		cout << "Point2D(" << m_x << "," << m_y << ")" << endl;
	}
	Point2D::~Point2D(){}
private:
	int m_x;
	int m_y;
};

class Point3D
{
public:
	Point3D(int x = 0, int y = 0, int z = 0)
	{
		m_p.m_x = x;
		m_p.m_y = y;
		m_z = z;
	}
	void print()
	{
		cout << "Point3D(" << m_p.m_x << "," << m_p.m_y << "," << m_z << ")" << endl;
	}
	Point3D::~Point3D(){}
private:
	Point2D m_p;
	int m_z;
};

//在Point2D中将print声明为友元函数,print可以访问Point2D隐藏成员
void print(const Point2D& point)
{
	cout << "我是外面的" << endl;
	cout << "Point2D(" << point.m_x << "," << point.m_y << ")" << endl;
}


int main(int argc, char* argv[])
{
	Point2D p21;
	p21.print();
	Point2D p22(10);
	p22.print();
	Point2D p23(20, 30);
	p23.print();
	print(p21);
	print(p22);
	print(p23);
	cout << "-------------------------" << endl;
	Point3D p31(10, 20, 30);
	p31.print();
	getchar();
}
#endif

只有一个构造函数,为什么能有三种方式?

友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元类

class Point3D
{
	friend void print(Point3D& point);
public:
	Point3D(int x = 0, int y = 0,int z=0)
	{
		m_p.m_x = x;
		m_p.m_y = y;
		m_z = z;
	}
	void print()
	{
		cout << "Point3D(" << m_p.m_x << "," << m_p.m_y << "," << m_z << ")" << endl;
	}
private:
	Point2D m_p;
	int m_z;
};

友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。

只有友元可以访问,其他依然不能访问。若类B是类A的友元。则只有类B的成员函数可以访问类A中的隐藏信息

友元是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。

友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

运算符重载

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

重载运算符可以使用成员函数或非成员函数(一般是友元函数)两种方法
只能使用其中的一种方法

#if 0
//复数类
class Complex
{
	friend Complex operator+(const Complex& cp1, const Complex& cp2);
	//operator>>(cin, cp1)
	//提起
	friend istream& operator>>(istream& in, Complex& cp1);
	//插入
	friend ostream& operator<<(ostream& out, Complex&cp1);
	//friend Complex& operator+=(Complex& cp1, const Complex& cp2);
public:
	Complex(double real = 0,double vir=0):m_real(real),m_vir(vir){}
	Complex& operator+=(Complex& cp2)
	{
	this->m_real += cp2.m_real;
	this->m_vir += cp2.m_vir;
	return *this;
	}
	//前++  先自增再返回
	Complex& operator++()   
	{
		//先对象自增
		this->m_real++;
		this->m_vir++;
		//再返回对象
		return *this;    
	}
	//后++  先返回再自增
	Complex operator++(int)  //返回引用返回对象    不返回引用返回对象的值
	{	
		Complex temp = *this;
		++(*this);	//对象自增
		return temp; //返回自增之前的值
	}

	void print()
	{
		cout << m_real << "+" << m_vir << "i" << endl;
	}
private:
	double m_real;	//实部
	double m_vir;	//虚部
};

Complex operator+(const Complex& cp1,const Complex& cp2)
{
	Complex temp;
	temp.m_real = cp1.m_real + cp2.m_real;
	temp.m_vir = cp1.m_vir + cp2.m_vir;
	return temp;
}

//Complex& operator+=(Complex& cp1, const Complex& cp2)
//{
//	cp1.m_real += cp2.m_real;
//	cp1.m_vir += cp2.m_vir;
//	return cp1;
//}
//提起
istream& operator>>(istream& in, Complex& cp1)
{
	in >> cp1.m_real >> cp1.m_vir;
	return in;
}
//插入
ostream& operator<<(ostream& out, Complex&cp1)
{
	out << cp1.m_real << "+" << cp1.m_vir << "i" << endl;
	return out;
}

int main()
{
	int a = 1, b = 2, c = 3;
	a = b + 2;
	Complex cp1(1, 2);
	Complex cp2(3, 4);

	cp1.print();
	
	//单目运算符  ++   --  cp1++
	//用友元函数形式重载   有一个参数 操作数就是参数    operator++(cp1)
	//用成员函数形式重载   没有参数(隐藏的this)  操作数就是当前调用对象  cp1.operator++() 
	
	int i = 1;
	cout << ++i << endl;  //2  先自增再返回
	cout << i << endl;	  //2
	cout << i++ << endl;  //2  先返回再自增
	cout << i << endl;	  //3
	
	//双目运算符  + - * /    cp1 += cp2
	//用友元函数形式重载  有两个参数 左操作数做第一个参数,右操作数做第二个参数 operator+=(cp1,cp2)
	//用成员函数形式重载  只有一个参数(隐藏的this) 左操作数是当前调用对象 右操作数做第一个参数
	//cp1.operator+=(cp2);
	cp1 += cp2;
	cp1.print();
	cp1 = cp1 + cp2; //operator+(cp1,cp2)
	cp1.print();
	//三目运算符   ?:
	//不能重载
	

	//不能创建新运算符  operator……&   不允许
	//不能改变优先级
	//  ::     .    .*      ?:     sizeof    不能重载

	//=     () []    -> ->*必须是成员函数


	//单目运算符,建议成员函数
	//双目运算符,建议友元重载
	cin >> cp1;  //    operator>>(cin,cp1)
	cout << cp1; //   operator<<(cin,cp1)
	//(cin >> a) >> a >> a;
	//(cout << a) << a << a;

	//getchar();
	system("pause");
	return 0;
}
#endif //运算符重载

explicit关键字 (了解)

#if 1
class A
{
	int m_a;
public:
	A(int a) :m_a(a){}
};

class B
{
	int m_b;
public:
	explicit B(int a) :m_b(a){}
};
int main()
{
	A a1 = 10;
	//B b1 = 10;
	B b2(10);
	getchar();
}
#endif //explicit关键字 了解

成员指针 (了解)

#if 0
class Integer
{
public:
	Integer(int i) :m_i(i)
	{

	}
	int m_i;
	int m_a;
	int m_c;
};
int main()
{
	Integer i1(10), i2(20);
	int* p1 = &i1.m_i;
	int* p2 = &i2.m_i;

	//成员指针  指向 成员变量 的指针
	//从远倒近 从右到左 括号优先
	//指向integer类中int类型的指针
	int Integer::* p = &Integer::m_i;
	i1.*p = 50;

	int Integer::*pp = NULL;
	cout << *(int*)&pp << endl;
	pp = &Integer::m_i;
	cout << *(int*)&pp << endl;
	pp = &Integer::m_a;
	cout << *(int*)&pp << endl;
	pp = &Integer::m_c;
	cout << *(int*)&pp << endl;
	getchar();
	return 0;
}
#endif //成员指针  了解

拷贝赋值 (重点)

#if 0
class Array
{
	friend ostream& operator<<(ostream& out, Array& arr);
public:
	Array(size_t size)
	{
		cout << "构造函数" << endl;
		m_array = new int[size];
		m_size = size;
	}
	Array(const Array& that)
	{
		cout << "拷贝构造函数" << endl;
		m_array = new int[that.m_size];
		memcpy(m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
		m_size = that.m_size;
	}
	//*p_array2 = *p_array1;//拷贝赋值

	Array& operator=(const Array&that)
	{
		cout << "拷贝赋值函数" << endl;
#if 0
		this->m_array = that.m_array;
		this->m_size = that.m_size;
		return *this;
#endif //编译器  浅拷贝

#if 0
		m_array = new int[that.m_size];
		memcpy(m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
		m_size = that.m_size;
		return *this;
		//原来内存没管 构造函数new了一次,没有delete,赋值又new了一次
#endif //初级菜鸟

#if 0
		//*p_array2 = *p_array2;   *p_array2.operator=(*p_array2);
		if (this->m_array)
		{
			delete this->m_array;
			this->m_array = NULL;
		}
		this->m_array = new int[that.m_size];
		memcpy(this->m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
		this->m_size = that.m_size;
		return *this;
		//没有考虑自赋值
#endif //高级菜鸟

#if 0
		//*p_array2 = *p_array2;   *p_array2.operator=(*p_array3);
		if (this != &that) //避免自赋值  this:p_array2    that: &(*p_array2) p_array3
		{
			if (m_array)//释放旧资源
			{
				delete m_array;
				m_array = NULL;
			}
			m_array = new int[that.m_size];//分配新资源
			//拷贝新资源
			memcpy(m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
			m_size = that.m_size;
		}
		//this:p_array2    that : &(*p_array2) p_array2   
		//返回自引用   
		return *this;  
		//*p_array2 = *p_array1;
		//new 可能失败
#endif //小鸟

#if 0
		if (this != &that) //避免自赋值 
		{
			int* ptemp = new int[that.m_size];
			memcpy(ptemp, that.m_array, that.m_size*sizeof(that.m_array[0]));

			if (m_array)//释放旧资源
			{
				delete m_array;
				m_array = NULL;
			}
			swap(ptemp, m_array);//ptemp NULL   m_array 新资源
			delete[] ptemp;
			m_size = that.m_size;
		}
		//返回自引用   
		return *this;
#endif //大鸟

		//老鸟
		if (this != &that) //避免自赋值 
		{
			Array temp(that); //利用拷贝构造   分配、拷贝新资源
			swap(this->m_array, temp.m_array);//交换新旧资源
		}//释放旧资源
		//返回自引用   
		return *this;
	}

	int& at(int index)
	{
		return m_array[index];
	}

	size_t size()
	{
		return m_size;
	}
	~Array()
	{
		if (m_array)
		{
			delete[] m_array;
			m_array = NULL;
		}
	}
private:
	int* m_array;
	size_t m_size;
};
ostream& operator<<(ostream& out, Array& arr)
{
	for (int i = 0; i < arr.m_size; ++i)
	{
		out << arr.m_array[i] << "\t";
	}
	out << endl;
	return out;
}
int main()
{
	Array* p_array1 = new Array(4);
	for (int i = 0; i < p_array1->size(); ++i)
	{
		p_array1->at(i) = i;
	}
	cout << *p_array1 << endl;

	Array* p_array2 = p_array1;   //指针赋值
	p_array2 = p_array1;		  //指针赋值
	p_array2 = new Array(*p_array1); //拷贝构造

	Array array3 = *p_array1;//拷贝构造
	
	*p_array2 = *p_array1;//拷贝赋值

	getchar();
	return 0;
}
#endif //拷贝赋值  重点

流输入<< 流输出>>操作符重载

cout是输出类的对象,而cin是输入类的对象
这些操作符必须重载为全局函数。如果操作符重载为一个成员函数,则它必须是对象的成员,且出现在操作符的左侧。而左侧一般是cout或者cin对象

重载运算符注意点

不可重载运算符,其余运算符都可以重载

  1. . 成员访问运算符
  2. .* 成员指针访问运算符
  3. :: 域运算符
  4. sizeof 长度运算符
  5. ?: 条件运算符
  • 运算重载符不可以改变语法结构。
  • 运算重载符不可以改变操作数的个数。
  • 运算重载符不可以改变优先级。
  • 运算重载符不可以改变结合性。
  • 不能创建新的运算符

赋值操作符重载

编译器提供默认拷贝赋值操作符重载。同默认拷贝构造一样,默认拷贝赋值操作符重载同样是浅拷贝。

  1. 避免自赋值
  2. 释放旧资源
  3. 分配新资源
  4. 拷贝新内容
  5. 返回自引用

尽量复用拷贝构造函数和析构函数中的代码
拷贝构造:分配新资源,拷贝新内容
析构函数:释放旧资源

尽量避免使用指针型成员变量。尽量通过引用和指针像函数传递对象型参数。降低参数传递开销同时,减少拷贝构造和拷贝赋值机会。

如果不需要进行拷贝构造和拷贝赋值,可将它们私有化,防止误用

如果为一个类提供了拷贝构造函数,那么也要提供拷贝赋值运算符函数

Demo: 一个时钟类,成员变量为时分秒。重载+ += - 三个运算符

class Clock
{
	friend Clock operator+(const Clock&, const Clock&);
	friend Clock operator-(const Clock&, const Clock&);
public:
	Clock(unsigned int hour = 0, unsigned int minute = 0, unsigned int second = 0);
	Clock& operator+=(const Clock&);  
	void show();
private:
	int m_hour;
	int m_minute;
	int m_second;
};
//全局重载+
Clock operator+(const Clock& c1, const Clock& c2)
{
	Clock cTemp;
	cTemp.m_hour = c1.m_hour + c2.m_hour;
	cTemp.m_minute = c1.m_minute + c2.m_minute;
	cTemp.m_second = c1.m_second + c2.m_second;
	while (cTemp.m_second>59)
	{
		cTemp.m_second %= 60;
		++cTemp.m_minute;
	}
	while (cTemp.m_minute>59)
	{
		cTemp.m_minute %= 60;
		++cTemp.m_hour;
	}
	while (cTemp.m_hour>23)
	{
		cTemp.m_hour %= 24;
	}
	return cTemp;
}
//全局重载-
Clock operator-(const Clock& c1, const Clock& c2)
{
	Clock cTemp;
	cTemp.m_hour = c1.m_hour - c2.m_hour;
	cTemp.m_minute = c1.m_minute - c2.m_minute;
	cTemp.m_second = c1.m_second - c2.m_second;
	while (cTemp.m_second<0)
	{
		cTemp.m_second += 60;
		--cTemp.m_minute;
	}
	while (cTemp.m_minute<0)
	{
		cTemp.m_minute += 60;
		--cTemp.m_hour;
	}
	while (cTemp.m_hour<0)
	{
		cTemp.m_hour += 24;
	}
	return cTemp;
}
//成员重载+=
Clock& Clock::operator+=(const Clock& time)
{
	//利用已经实现的+
	*this = *this + time;
	return *this;   //+=最后返回左操作数
}

void Clock::show()
{
	cout << setw(2) << setfill('0') << this->m_hour << ":"    //格式化输出  需要#include <iomanip>
		<< setw(2) << setfill('0') << this->m_minute << ":"
		<< setw(2) << setfill('0') << this->m_second << endl;
}

调用

显示类型转换

C风格的显示类型转换
(目标类型)源类型变量

C++语言中新增了四个关键字static_cast、const_cast、reinterpret_cast和dynamic_cast。新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。

常类型转换

const_cast<目标类型>(源类型变量)
去掉指针或引用上的const属性

重解释类型转换

reinterpret_cast<目标类型>(源类型变量)

改变指针或引用的类型
将指针或引用转换为一个足够长度的整形
将整型转换为指针或引用类型。

C++ ——继承

3承的概念

个性和共性

在这里插入图片描述

基类和子类

继承类型和派生类型

#if 0
class A
{
public:
	A()
	{
		cout << this << endl;
		cout << "A构造" << endl;
	}
};

class Human :public A
{
public:
	/*Human()
	{
		cout << this << endl;
		cout << "Human构造" << endl;
	}*/
	Human(string name, int age, char gender = '0') :m_name(name), m_age(age), m_gender(gender)
	{
		cout << this << endl;
		cout << "Human三参构造" << endl;
	}
	void eat()
	{
		cout << "吃吃吃" << endl;
	}
	void sleep()
	{
		cout << "睡睡睡" << endl;
	}
protected:
	string m_name;
	char m_gender;   //0  1
private:
	int m_age;
};

class Student :public Human
{
public:
	//通过初始化表显示选择基类构造函数
	Student(string name) :Human(name, 18, '0')
	{
		cout << this << endl;
		cout << "Student构造" << endl;
		m_name = name;
	}
	void study()
	{
		Human::eat();	//可以访问公有成员
		m_name = "橘子";//可以访问保护成员
		//m_age=10;//私有成员存在但是不能访问
		cout << "学习" << endl;
	}
	//int eat;//子类成员会隐藏基类同名成员
	int a;
};

int main()
{
	Student stu("努力");
	//构造子类对象顺序  为整个对象分配内存  构造基类子对象 构造子类成员 执行构造代码
	//											|		
	//									为整个对象分配内存	构造基类子对象 构造子类成员 执行构造代码
	//子类对象任何时候都可以被当成基类类型对象 ISA 皆然
	Human* ph1 = &stu;
	Human& rh1 = stu;
	getchar();
	return 0;
}
#endif //继承和子类构造析构顺序

继承语法

Class 子类:继承方式1 基类1,继承方式2 基类2{
}

继承方式

公有继承:public

子类对象任何时候都可以被当作基类类型对象

访问范围缩小在编译器看来是安全的
基类类型指针或者引用不能隐式转换成子类对象

访问范围扩大在编译器看来是危险的
强转可以,但是对于派生内容访问有危险。

基类指针或引用到底是基类对象还是子类对象,需要自己判断,不能依靠编译器判断
子类可以直接访问基类的所有公有和保护成员,其效果如同它们是在子类中声明一样。对于基类的私有成员,在子类中存在但不能访问。
在子类中定义基类中同名的公有成员或保护成员,子类中的成员会隐藏基类同名成员。想访问被隐藏的成员,可以借助作用域限定符“::”

保护继承:protected

使基类公有成员和保护成员进行保护化,只禁止外部通过该子类访问。子类指针或引用不能隐式转换成基类类型指针或引用

私有继承:private

将基类公有和保护成员私有化,禁止外部通过该子类访问。也禁止该子类的子类访问。子类指针或引用不能隐式转换成基类类型指针或引用

继承方式与访问控制

类成员访问控制限定符和访问控制属性

访问控制限定符访问控制属性基类子类外部友元
public公有成员OKOKOKOK
protected保护成员OKOKNOOK
private私有成员OKNONOOK

继承方式与访问关系

继承方式基类public成员基类protected成员基类private成员
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateprivateprivateprivate

通过子类访问继承基类成员时,需要考虑继承方式与访问控制属性

子类构造和析构

子类构造

子类构造函数会调用基类构造函数,构造子类对象中的基类子对象

子类构造函数没有显示指明基类构造方式,会选择基类的缺省构造函数

子类显示调用基类构造函数可以在初始化表中显示指明构造方式。

  • 子类对象构造过程

  • 构造基类子对象,构造成员变量,执行构造代码

  • 基类构造函数定义为私有,子类无法实例化对象,可以阻断类的继承。

子类析构

子类析构会调用基类析构。

子类对象析构过程

  • 执行析构代码,析构成员变量,析构基类子对象
  • 通过基类指针析构子类对象,只会析构子类对象中的基类子对象。可能造成内存泄漏

子类拷贝构造和拷贝赋值

  • 子类没定义拷贝构造函数:调用基类默认拷贝构造
  • 子类定义拷贝构造函数,没指定基类构造方式:调用基类默认构造函数构造子类对象中基类子对象
  • 子类定义拷贝构造函数同时指定基类部分以拷贝构造方式构造:子类对象基类部分和扩展部分都可以复制

多继承

内存布局从低到高,析构顺序相反

菱形继承

C里面有两个Base,调用Base方法会调用歧义
派生多个中间子类的公共基类子对象,在继承自多个中间子类的汇聚子类对象中,存在多个实例。
在汇聚子类或通过汇聚子类对象访问基类成员,因继承路径不同而导致不一致。

虚继承

防止继承中成员访问的二义性
在A B继承方式前加关键字virtual
将Base的数据保存在一个公共位置

C++ ——多态

虚函数

普通成员函数前加关键字virtual,称为虚函数

#if 0
//基类 Animal
class Animal
{
public:
	Animal(string name);
	virtual void say();  //虚函数
protected:
	string m_name;
};
Animal::Animal(string name) :m_name(name){}

void Animal::say()
{
	cout << m_name << ":^(*^&@*(!" << endl;
}

//派生类 Dog
class Dog :public Animal
{
public:
	Dog(string name);
	void say();//也是虚函数  形成覆盖
};
Dog::Dog(string name) :Animal(name){}
void Dog::say()
{
	cout << m_name << ":汪汪汪" << endl;
}


int main()
{
	Animal* p = new Animal("动物");
	p->say();
	delete p;

	//基类指针指向子类对象
	p = new Dog("狗");
	p->say();

	Animal a = *p;
	a.say();
	getchar();
	return 0;
}

/*
编译器按实际类型调用
将say()声明成虚函数
基类指针指向什么对象就调用相应对象的成员函数
*/
#endif //虚函数

覆盖

子类成员函数和基类的虚函数具有相同函数原型,该成员函数也就是虚函数,无论其是否带有virtual关键字,都对基类虚函数构成覆盖

	函数必须是成员函数(非全局和静态成员函数)
	
   基类使用virtual声明
   
   覆盖版本和基类版本函数原型必须严格相同

多态

子类提供了对基类虚函数的有效覆盖,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际上调用的将是子类中的覆盖版本,而非基类中的原始版本

意义:一般情况下调用哪个类的成员函数由调用者指针或引用本身类型决定,当多态发生,调用哪个类成员函数完全由调用者指针或引用的实际目标对象的类型决定

条件 基类定义虚函数,借助指针和引用

调用虚函数的指针也可能是基类中的this指针,同样满足多态条件,但在构造和析构函数中除外

#if 0
//单态
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int divi(int x, int y)
{
	if (y != 0)
	{
		return x / y;
	}
	return 0;
}
//怎么用一个函数实现加减乘除?现有知识\
//多态函数
int calc(int x, int y, int(*fun)(int, int))
{
	return fun(x, y);
}
int main()
{
	cout << calc(1, 2, add) << endl;
	cout << calc(5, 3, sub) << endl;
	cout << calc(4, 5, mul) << endl;
	cout << calc(12, 6, divi) << endl;
	getchar();
	return 0;
}
#endif //多态函数

纯虚函数

形如Virtual 返回值 函数名(形参表)=0;的虚函数,成为纯虚函数或抽象方法

#if 0
class A
{
public:
	A()
	{
		cout << "A构造" << endl;
	}
	virtual ~A()
	{
		cout << "A析构" << endl;
	}
};
class B :public A
{
public:
	B()
	{
		p = new int(10);
		cout << "B构造" << endl;
		
	}
	~B()
	{
		cout << "B析构" << endl;
		if (p)
		{
			delete p;
		}
	
	}
private:
	int* p;
};

int main()
{
	//基类指针指向子类对象
	A* p = new B;
	delete p;
	getchar();
	return 0;
}
#endif //虚析构

抽象类

至少拥有一个纯虚函数的类成为抽象类
抽象类不能实例化为对象
抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类

#if 0
//单态
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int divi(int x, int y)
{
	if (y != 0)
	{
		return x / y;
	}
	return 0;
}
//怎么用一个函数实现加减乘除?现有知识\
//多态函数
int calc(int x, int y, int(*fun)(int, int))
{
	return fun(x, y);
}
int main()
{
	cout << calc(1, 2, add) << endl;
	cout << calc(5, 3, sub) << endl;
	cout << calc(4, 5, mul) << endl;
	cout << calc(12, 6, divi) << endl;
	getchar();
	return 0;
}
#endif //多态函数

纯抽象类(接口)

全部由纯虚函数构成的抽象类成为纯抽象类或接口

#if 0
//纯抽象类
class A   
{
public:
	virtual void display(int) = 0; //纯虚函数 抽象方法
	virtual void foo(int) = 0;
};
class B:public A
{
public:
	void display(int)
	{
		cout << "B" << endl;
	}
	void foo(int){}
};

class C :public A
{
public:
	void display(int)
	{
		cout << "C" << endl;
	}
};


int main()
{
	A* p = new B;
	p->display(10);
	delete p;

	//p = new C;   //抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类

	//p->display(10);
	delete p;
	//A a;//抽象类不能实例对象   类中至少有一个抽象方法
	getchar();
}
#endif //纯虚函数和抽象类

虚函数表

#if 1
class N
{
public:
	void foo()
	{
		cout << "N::foo" << endl;
	}
	void bar()
	{
		cout << "N::bar" << endl;
	}
	int m_a;
	int m_b;
};
class A
{
public:
	virtual void foo()
	{
		cout << "A::foo" << endl;
	}
	virtual void bar()
	{
		cout << "A::bar" << endl;
	}
	int m_a;
	int m_b;
};

class B :public A
{
public:
	void foo()
	{
		cout << "B::foo" << endl;
	}
	void bar()
	{
		cout << "B::bar" << endl;
	}
};

int main()
{
	N n;
	A a;
	B b;
	A* pa = &b;
	cout << "sizeof(N):" << sizeof(N) << ",m_a:" << offsetof(N, m_a) << ",m_b:" << offsetof(N, m_b) <<endl;

	cout << "sizeof(A):" << sizeof(A) << ",m_a:" << offsetof(A, m_a) << ",m_b:" << offsetof(A, m_b) << endl;

	void* vf_ptr = *(void**)&a;
	cout << vf_ptr << endl;

	typedef void(*VFUN) (void*);   //VFUN     void(*)(void*)  //函数指针
	typedef VFUN* VPTR;			//VPTR  void(**) (void*)  //指像函数指针类型的指针  虚函数表

	VPTR _vfptr = *(VPTR*)&a;
	cout << _vfptr << endl;
	a.foo();
	_vfptr[0](&a);
	_vfptr[1](&a);
	

	VPTR _vfptr1 = *(VPTR*)&b;
	cout << _vfptr1 << endl;
	b.foo();
	_vfptr1[0](&b);
	_vfptr1[1](&b);

	getchar();
	return 0;
}
#endif //虚函数表 

Demo:小型公司月薪计算

某小型公司,主要有四类员工(Employee):经理(Manager)、技术人员(Technician)、销售经理(SalesManager)和推销员(SalesMan)。现在,需要存储这些人员的姓名(name)、编号(id)、当月薪水(salary)。计算月薪总额并显示全部信息。人员编号基数为 1000,每输入一个人员工信息编号顺序加 1。

月薪算法:
经理拿固定月薪 8000 元;
技术人员按每小时 100 元领取月薪;
推销员的月薪按该推销员当月销售额的 4%提成;
销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的5%。

class Employee
{
public:
	Employee():m_name(""),m_salary(0.0f) {
		m_num++;	//每有一个员工m_num++
		m_id = m_num;
	}
	virtual void getSalary() = 0;//员工的工资计算没有具体方法 使用纯虚函数
	void show() {
		cout << "姓名:" << m_name << " 员工ID:" << m_id << " 工资:" << m_salary << endl;
	}
	virtual ~Employee() {}

protected:
	string  m_name;
	int m_id;
	float m_salary;
	static int m_num;   //属于全体员工 不在属于某个员工对象
};
int Employee::m_num = 0;

//经理拿固定月薪 8000 元;
class Manager :virtual public Employee
{
public:
	Manager(string name) {
		m_name = name;
		m_baseSalary = 8000.0f;
	}
	void getSalary() {
		m_salary = m_baseSalary;
	}

	~Manager() {}
protected:
	float m_baseSalary;			//基本工资
};

//技术人员按每小时 100 元领取月薪;
class Technician : public Employee
{
public:
	Technician(string name, int hour) {
		m_name = name;
		m_hour = hour;
	}
	void getSalary() {
		m_salary = m_hour * 70;
	}

	~Technician() {}
private:
	int m_hour;
};


//推销员的月薪按该推销员当月销售额的 4%提成;
class SalesMan : virtual public Employee
{
public:
	SalesMan(string name, float Count) {
		m_name = name;
		m_Count = Count;
		m_partCount += m_Count;
	}
	void getSalary() {
		m_salary = m_Count * 0.04;
	}

	~SalesMan() {}
private:
	float m_Count;
protected:
	static float m_partCount;						//属于全体销售 不在属于某个销售对象
};
float SalesMan::m_partCount = 0.0f;


//销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的5%。
class SalesManager : public Manager, public SalesMan
{
public:
	SalesManager(string name):Manager(name),SalesMan(name,0) {
		m_name = name;
		m_baseSalary = 5000;
	}
	void getSalary() {
		m_salary = m_baseSalary + m_partCount*0.05;
	}

	~SalesManager() {}
};

int main()
{
	Employee* emp[5] = { 0, };
	emp[0] = new Manager("关羽");
	emp[0]->getSalary();
	emp[0]->show();

	emp[1] = new Technician("张飞", 99);
	emp[1]->getSalary();
	emp[1]->show();

	emp[2] = new SalesMan("赵云", 60000);
	emp[2]->getSalary();
	emp[2]->show();

	emp[3] = new SalesMan("马超", 900000);
	emp[3]->getSalary();
	emp[3]->show();

	emp[4] = new SalesManager("黄忠");
	emp[4]->getSalary();
	emp[4]->show();

	for (int i = 0; i < 5; ++i)
	{
		delete emp[i];
	}
	return 0;
}

IO流

继承关系

iostream标准输入输出(控制台窗口)

<< 插入操作符 向流中输出表达式的值。

当输出流是cout,则输出到显示器
put( ) 输出单个字符
write(buf, len) 输出指定长度

>> 提取操作符,从流中提取出数据赋给变量.

get( )操作:读取单个字符
getline( )读取一行
read(buf, len) 对空白字符(包括’\n’)照读不误
peek():查看而不读取

流格式化

使用流的setf成员来设置,使用unsetf来取消

  • ios::skipws 跳过空格(输入流的默认情况,会跳过输入的空格)
  • ios::showbase 打印整型值时指出数字的基数,比如八进制输出的话就在前面加个0,十六进制输入就在前面加个0x
  • ios::showpoint 显示浮点值的小数点并阶段数字末尾的零
  • ios::uppercase 显示十六进制数值的时,使用大写A~F来代替
  • ios::showpos 显示整数前的加号
  • ios::unitbuf 单元缓冲区,每次插入后刷新流

宽带,填充字符,和精度

fstream 文件输入输出

文件流的打开模式

  • ios::in 打开输入文件,使的现存的文件不会被截断
  • ios::out 打开输出文件,意味值是ios::trunc模式
  • ios::app 打开文件,进行追加
  • ios::ate 打开文件,指向文件末尾
  • ios::trunc 打开文件,文件存在就截断旧文件
  • ios::binary 按照二进制方式打开文件,默认打开为文本方式

文件流的定位

  • ios::beg 流的开始位置
  • ios::cur 流的当前位置
  • ios::end 流的末端位置

sstream字符串输入输出(内存中)

Demo:将解压有多个文件的压缩包

#include <fstream>
struct fileinfo
{
	int fileNameSize;
	int fileOff;
	int fileSize;
	char fileName[20];
};

int main(int argc, char* argv[])
{
	fstream file("new.pack", ios::in | ios::binary);//打开文件  此处的文件是new.pak

	//1.读取索引表大小  +索引表个数
	int listSize, listNum;
	file.read((char*)&listSize, 4);//需要强转  &取地址
	file.read((char*)&listNum, 4);
	cout << listSize << "\t" << listNum << endl;

	//2.读取索引表 创建文件
	fstream *srcfile = new fstream[listNum];//文件
	fileinfo *src = new fileinfo[listNum];//存放索引表
	for (int i = 0; i < listNum; i++)
	{
		file.read((char*)&src[i].fileSize, 4);//读取文件大小
		file.read((char*)&src[i].fileOff, 4);//读取文件偏移量
		file.read((char*)&src[i].fileNameSize, 4);//读取文件名大小
		file.read(src[i].fileName, src[i].fileNameSize);//读取文件名
		srcfile[i].open(src[i].fileName, ios::out | ios::binary);//创建文件
		//cout << "new file" << src[i].fileName << endl;
	}

	//3.读取文件  从pack文件中读取到新文件中
	for (int i = 0; i < listNum; i++)
	{
		for (int j = 0; j < src[i].fileSize; j++)//读取一个文件
		{
			srcfile[i].put(file.get());//从文件中读取
		}
	}
	//4 关闭文件
	for (int i = 0; i < listNum; i++)
	{
		srcfile[i].close();
	}
	delete[] srcfile;//释放内存
	delete[]src;
	return 0;
}

异常

语法错误,逻辑错误,功能错误,设计错误,需求不符,环境异常,操作错误。。。

异常处理

异常处理主要处理运行环境中发生,但是在设计,编码和测试时无法预料得潜在得异常

  1. 利用返回值返回错误信息
    优点:所有局部对象都能正确被析构(析构函数正确)
    缺点:每层都要判断,流程繁琐
  2. 借助setjmp/longjmp远程跳转
    优点:简单,一步到位
    缺点:局部对象可能不会被析构
  3. 抛出-捕获异常对象
    优点:简单,一步到位。
    逐层析构,避免内存泄漏
  4. 异常处理语法
    抛出异常
    throw 异常对象; 可以是基本类型对象,也可以是类类型的对象。但是不要抛出局部对象的指针。
  5. 捕获异常
try{
	可能引发异常的语句;
}
catch(异常类型1& ex){
	对异常类型1的异常处理
}
catch(异常类型2& ex){
	对异常类型2的异常处理
}
。。。

根据异常对象类型从上到下匹配,而非最优匹配,因此对子类类型异常的捕获不要放在对基类类型异常的捕获后面

建议使用引用接收异常对象,避免因为拷贝构造带来性能损失或者新的异常。

为每一种异常定义相对应的异常类型,异常对象必须允许被拷贝构造和析构,最好从标准库异常派生异常。

标准库异常


  • what():返回一个C风格的字符串,目的是为抛出的异常提供文本描述

  • logic_error派生

  • class domain_error; 违反了前置条件

  • class invalid_argument; 指出函数的一个无效参数

  • class length_error; 指出有一个超过类型size_t的最大可表现值长度的对象的企图

  • class out_of_range; 参数越界

  • runtime_error派生

  • class range_error; 违反后置条件

  • class overflow_error; 报告一个算术溢出

  • class underflow_error; 报告算术下溢错误。

  • class bad_cast; 在运行时类型识别中有一个无效的dynamic_cast表达式

  • class bad_typeid; 报告在表达试typeid(*p)中有一个空指针p

  • class bad_alloc; 存储分配错误

静态类型转换

static_cast<目标类型>(源类型变量)

实现C++中内置基本数据类型之间的相互转换.(隐式类型转换的逆转换)

把空指针转换成目标类型的空指针

把任何类型的表达式转换为void类型

用于类层次结构中基类和派生类之间指针或引用的转换

进行上行转换(把派生类的指针或引用转换成基类表示)是安全的

进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
static_cast不能转换掉const属性

动态类型转换

dynamic_cast<目标类型>(源类型变量)

将基类类型指针或者引用转换为子类类型的指针或引用。前提是子类必须从基类多态继承,即基类至少有一个虚函数。(从虚函数表找类型信息)

目标为期望得到的子类类型对象,转换成功,否则转换失败

针对指针的动态类型转换,失败返回NULL,针对引用的动态类型转换,失败抛出bad_cast异常

C++11新特性

long long

64字节整形

nullprt

空指针,以前NULL只是用0代替,不能完整表示空的意思

类型别名

给类型取别名 using dtype = int;

auto类型修饰符

根据初始化代码的内容自动判断变量的类型,而不是显式的指定

类内初始化

类内初始化之后,构造函数只需要负责和缺省值不同的部分

范围for语句

使用条件:支持begin和end。
STL容器都支持,基本类型数组也有类似机制。

返回类型后置

原来放返回值类型的位置写auto,在函数声明结束以后接一个’->'再跟着写函数的返回值类型。和普通声明效果一样。

生成默认函数

添加了其他有参数的构造函数,编译器就不再生成缺省的构造函数了。C++11允许我们使用=default来要求编译器生成一个默认构造函数
A()=default;

已删除的函数

假如上面的几个函数中,不想使用其中某个,可以将其定义为private,或者使用=delete。

A()=delete;

委托构造函数

真正的构造工作由最后一个构造函数完成,而其他的构造函数都是委托最后一个构造函数完成各自的构造工作

右值引用

一个表达式可以放在赋值语句的左侧,就称之为左值,如果不能放到表达式的左侧,就称之为右值
C++11使用&&来声明右值引用
int &&r=1;

lambda表达式

没有函数名,有参数和函数体

智能指针

将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

auto_ptr(危险,被弃用)

unique_ptr 独占指针,离开作用域后释放指针
实现了独占式拥有概念,意味着它可确保一个对象和其相应资源同一时间只被一个指针拥有。一旦拥有者被销毁或变成空,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源也会被释放。

shared_ptr共享指针,增加计数器,计数器为0时释放指针
多个shared_ptr可以共享(或说拥有)同一对象。对象的最末一个拥有者有责任销毁对象,并清理与该对象相关的所有资源。
shared_ptr的目标就是,在其所指向的对象不再被使用之后,自动释放与对象相关的资源。

override

如果派生类在虚函数声明时使用了override描述符,那么该函数必须重写其基类中的同名函数,否则代码将无法通过编译。

final

类被final修饰,不能被继承
虚函数被final修饰,不能被override

模版

#pragma once
template<class T, typename T1>
int bar(T a, T1 b);

template<class T, typename T1>
int bar(T a, T1 b)
{
	cout << typeid(a).name() << " " << typeid(b).name() << endl;
	return a > b ? a : b;
}

int add(int a, int b);

泛型

泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。

函数模版化

值参数化

数据的值可以通过函数参数传递,在函数定义时数据的值是未知的,只有等到函数调用时接收了实参才能确定其值。这就是值的参数化。

int max(int a, int b)
{
	return a > b ? a : b;
}

类型参数化

在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推断数据类型。这就是类型的参数化。

template<typename T>
T max(T a, T b)
{
	return a > b ? a : b;
}
max<int>(12, 34)

函数模版

函数模板代表一个函数族,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个标识符来代替函数调用时根据实参逆推出真正类型,生成具体函数的二进制代码。

函数模版语法

template <typename 类型参数1 , typename 类型参数2 , ...> 
返回值类型  函数名(形参列表){
    //模板函数体
}

类型参数用<> 值参数用()
typename可以用class替换.此处class不是表示类,因为早期C++没有typename而是用class表示类型参数名字。

函数模版的使用

 函数名<参数类型1,参数类型2>(形参表)

编译器根据调用函数模板提供的模板实参,将所调用的函数模板编译成具体函数的过程,称为函数模板的实例化。

用于实例化函数模板的类型必须满足模板内部基于该类型的操作。

函数模板二次编译。编译器第一次看到函数定义,检查语法,生成内部结构。第二次,将提供的具体类型实参结合之前内部结构生成具体函数二进制指令。

对于函数模板一定要让编译器看到调用语句的同时,也能看到定义代码,否则无法完成第二次编译。因此,将函数模板定义放到头文件,需要使用该函数模板的源文件都包含此头文件。

函数模板隐式推断(模板函数)

max(12, 34)隐式推断成max
隐式推断的同时不允许隐式转换

Demo:实现一个模板函数cmp,该函数比较两个元素的任何类型。

实现一个模板函数cmp,该函数比较两个元素的任何类型。并使它适合于比较任何两个int、double、float、char*、string和任何两个相同类型的指针。如果第一个参数等于第二个参数,则返回true,否则返回false。
1.当比较两个int、double、float、char*、string时,应该比较它们的值
2 在比较两个指针时,应该比较它们指向的值。

#include "string"
#include "cstring"
template<typename T>
bool cmp(T a, T b) {
    return a == b;
}

template<typename T>
bool cmp(T* a, T* b) {
    return *a == *b;
}

bool cmp(char a[], char b[]) {
return !strcmp(a, b);
}

类模板

将类中成员变量的类型,成员函数的类型,成员类的类型,基类的类型等参数化。

一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。

类模版格式

template<typename 类型参数1 , typename 类型参数2 ,> class 类名{
    //TODO:
};

成员函数如果在类外定义,同样要带上模板头
template<typename 类型参数1 , typename 类型参数2 ,>
返回值类型 类名<类型参数1 , 类型参数2, ...>::函数名(形参列表){
    //T

使用类模板

使用类模板,必须显示指定模板参数。不支持隐式推断

类模板可以带有缺省值

template<typename T = int>
class Stack
{}

Stack<> si;

类模板中,只有那些被调用的成员函数才被实例化,即编译二进制代码。某些类型虽然没有提供某些功能,照样可以实例化该类模板,只要不直接或间接调用那些未提供功能的成员函数即可。

类模板的静态成员变量,在该类模板的每个实例化类中,都有一份独立的拷贝。

模板特化

C++中的模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化

将泛型的东东搞得具体化一些

全特化,就是模板中模板参数全被指定为确定的类型。定义了一个全新的类型

偏特化,就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。类型上加上const、&、( cosnt int、int&、int、等等)并没有产生新的类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JWT 实现登录认证 + Token 自动续期方案

前言 过去这段时间主要负责了项目中的用户管理模块&#xff0c;用户管理模块会涉及到加密及认证流程。今天就来讲讲认证功能的技术选型及实现。技术上没啥难度当然也没啥挑战&#xff0c;但是对一个原先没写过认证功能的菜鸡来说也是一种锻炼吧。 技术选型 要实现认证功能&a…

【短篇】函数重载

函数重载 重载使用规则 重载 函数重载也是CPP相较于C的一个优化内容。 在C中我们都知道函数名不能重名 当然这个错误对于我还有类似我这种只学过C的人来说&#xff0c;这个错误是显得多么理所当然。 但是在Cpp中&#xff0c;则对这个功能进行了优化 看到这就有人问了&#x…

我去蔚来试驾了

前面写了比亚迪汉、小鹏P7i的试驾体验&#xff0c;链接如下&#xff1a; 小鹏P7I试驾体验&#xff01; 今天接着分享蔚来ET5的试驾体验&#xff0c;实话实说&#xff0c;我是蔚来ET5的颜粉&#xff0c;颜值也是ET5最大的卖点之一。 我身边不少朋友&#xff0c;不管是男生还是女…

windows下使用vite搭建vue开发环境

windows下使用vite搭建vue开发环境 1 下载安装配置NodeJS1.1 下载1.2 安装1.3 配置1.4 npm镜像加速配置1.6 设置环境变量 2 Vite安装 1 下载安装配置NodeJS 1.1 下载 下载地址&#xff1a;https://nodejs.cn/download 到NodeJS官网&#xff0c;选择Windows安装包即可。 1.2…

网络协议-UDP vs TCP, HTTP2.0和3.0

目录 TCP vs UDP Internet协议群&#xff08;TCP/IP协议群&#xff09; 传输层和网络层 User Data Diagram&#xff08;UDP&#xff09; 连接 重发&#xff08;校验&#xff09; UDP不保证顺序 思考&#xff1a;看了上面的几点&#xff0c;UDP没有虚拟连接、不校验数据、…

Vue ElementUI Axios 前后端案例(day01) 之Vue

前言js 问js是什么&#xff0c;他有什么作用&#xff0c;与html和css的区别是什么 JavaScript&#xff08;简称JS&#xff09;是一种脚本语言&#xff0c;用于在网页上实现交互效果、动态效果和动态数据更新等功能。它是一种解释性语言&#xff0c;需要在浏览器中解释和执行。…

【Paper Note】Swin Transformer: Hierarchical ViT using Shifted Windows

Swin Transformer: Hierarchical ViT using Shifted Windows 概述核心思想整体结构名词解释与vit区别 模型处理过程概括Patch EmbeddingBasicLayerPatch MergingSwin Transform BlockWindow AttentionShifted Window Attention小结 模型使用及代码模型使用环境配置SwinT 代码Pa…

跨境卖家都要知道的:对话式销售

买家可以用他们的指纹登录大多数东西&#xff0c;并通过与它交谈来管理他们的日历。这些人不会填写一份表格&#xff0c;如果他们填写的字段越多&#xff0c;表格的长度就会越长。如果他们知道只会受到骚扰&#xff0c;他们当然不会下载某些东西。 相反&#xff0c;他们更喜欢…

[Linux系统]系统安全及应用一

系统安全及应用一、账号安全基本措施1.1系统账号清理1.1.1将非登录用户的shell设为/sbin/nologin1.1.2锁定长期不使用的账号1.1.3删除无用的账号1.1.4锁定账号文件文件chattr1.1.5查看文件校验和md5sum1.2密码安全控制1.2.1设置密码有效期1.3历史命令限制1.3.1 减少记录命令的条…

5GHz无线局域网系统模拟

移动电视双天线分集接收技术 随着DVB-T在手机电视、车载电视、楼宇电视、地铁电视等户外广播领域内的发展&#xff0c;在这些接收范围内&#xff0c;多径衰落、多普勒频移等小范围衰落是不可避免的问题&#xff0c;解决这些衰落和干扰成为倍受关注的问题。为了解决衰落&#x…

rk3568 点亮LCD (BT656 BT1120)

rk3568 适配 BT656/BT1120 BT.656 TX 和 BT.1120 TX&#xff0c;是一种并行输出接口&#xff0c;而 Camera 对应的是 BT.656 RX和 BT.1120 RX&#xff0c;是一种并行输入接口&#xff0c;两则在协议上是一致的。与同为并口的RGB非常像&#xff0c;在rk3568 芯片上RGB和BT656/B…

【jenkins】Jenkins连接 Gitlab实现 push代码自动构建

目录 一、安装插件 二、构建任务 三、为任务配置触发器 四、到gitlab进行设置webhooks 4.1 设置网络 4.2 到jenkins对应项目的源码库 4.3 测试 4.3.1 点击测试--标签推送事件 4.3.2 点击编辑 一、安装插件 持续部署的第一步需要检查是否安装gitlab插件&#xff1a; gitla…

Doris(9):删除数据(Delete)

Delete不同于其他导入方式&#xff0c;它是一个同步过程。和Insert into相似&#xff0c;所有的Delete操作在Doris中是一个独立的导入作业&#xff0c;一般Delete语句需要指定表和分区以及删除的条件来筛选要删除的数据。 Doris 目前可以通过两种方式删除数据&#xff1a; DE…

记录-JavaScript常规加密技术

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 当今Web开发中&#xff0c;数据安全是一个至关重要的问题&#xff0c;为了确保数据的安全性&#xff0c;我们需要使用加密技术。JavaScript作为一种客户端编程语言&#xff0c;可以很好地为数据进行加…

Spring Boot 安全

目录 1.概述 2.token 2.1.理论 2.2.使用 3.JWT 3.1.理论 3.2.使用 4.oauth 5.Spring Security 5.1.概述 5.2.基本认证授权 5.3.加密 1.概述 在后端来说&#xff0c;安全主要就是控制用户访问&#xff0c;让对应权限的用户能访问到对应的资源&#xff0c;主要是两点…

AOP通知中获取数据

AOP通知中获取数据 之前我们写AOP仅仅是在原始方法前后追加一些操作&#xff0c;接下来我们要说说AOP中数据相关的内容&#xff0c;我们将从获取参数、获取返回值和获取异常三个方面来研究切入点的相关信息。 获取切入点方法的参数&#xff1a;所有的通知类型都可以获取参数 …

Vulhub开源漏洞靶场用Java远程访问

事件起因&#xff0c;被迫参加某竞赛&#xff0c;中途发现&#xff0c;全员摸鱼&#xff0c;遂一起摸鱼Vulhub是一个面向大众的开源漏洞靶场&#xff0c;无需docker知识&#xff0c;简单执行一条命令即可编译、运行一个完整的漏洞靶场镜像。 Installation 在Ubuntu 20.04下安…

JVM 垃圾回收详解之内存分配和回收原则+死亡对象判断方法

前言 当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时&#xff0c;我们就需要对这些“自动化”的技术实施必要的监控和调节。 堆空间的基本结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时&#xff0c;Java 自动内存管理最核…

【STM32】基础知识 第七课 存储器映射 寄存器映射

【STM32】基础知识 第七课 存储器映射 & 寄存器映射 STM32 寻址范围存储器映射存储器功能划分 (F1 为例)Block 0Block 1Block 2寄存器映射 寄存器映射 (F1 为例)寄存器映射举例寄存器地址计算GPIO 外设基地址及偏移量寄存器地址及偏移量寄存器地址计算过程 使用结构体映射寄…

《2-数组》

数组 1.简介&#xff1a; 数组&#xff08;Array&#xff09;是一种固定长度的存储相同数据类型在连续内存空间中的数据结构 引出&#xff1a;[索引 &#xff08;Index&#xff09;]----元素在数组中的位置 2.初始化 写法&#xff1a;一般用到无初始值、给定初始值 在不给定…