c++笔记

C++语言基础笔记

基础知识

快捷键说明
F9切换断点
Ctrl+F5开始执行(不调试)
F5开始调试、继续(跳过一个断点)
Shift+F5停止调试
Ctrl+shift+F5重启调试
F10step over
F11step into 逐步运行
Shift+F11step out
Ctrl+J智能提示
Tab选择和使用智能提示内容
(Ctrl+M) , M保持Ctrl不松开,先按下M,再按下M ;折叠或展开当前方法
(Ctrl+M) , O保持Ctrl不松开,先按下M,再按下O ;折叠所有
(Ctrl+M), L保持Ctrl不松开,先按下M,再按下L ;展开所有
语法须知
/*
源文件扩展名:cpp
入口:main函数
java必须先有类,再有方法(函数)
 /* . */ 左边只能是对象
//为什么内存里面的值默认的都是ccccccc -858993460
中断:interrupt
cc->int3:起到断点的作用,防止代码执行到函数的时候出现乱码
*/
#define _CRT_SECURE_NO_WARNINGS//防止在使用C的方法时报错

#include <iostream>
using namespace std;
struct Person{
    int m_age;
};
struct Student:Person{
    int m_no;
};


int main()
{
    Student student;
    
    
    cout<<""<<endl;
    system("pause")//阻塞功能
    return 0;//返回正常退出 EXIT_SUCCESS
}


C++对C语言的增强
//1.全局变量检测增强
int a;
int a=10;
//2.函数检测增强,参数类型增强,返回值检测增强,函数调用参数检测增强
int getRectS(w,h){
    return w*h
}
void test(){
    getRects(10,10,10)
}
//3.类型转换检测增强,在C++里面需要强转(char*)
void test(){
    char *p  = malloc(sizeof(93)) //malloc返回值是void*
}
//4.struct增强
struct Person{
    int m_Age;
    void plusAge();//c语言中strut不可以加函数,但C++中可以
}
void test01(){
    struct Person p1;//c语言中使用时候必须加入struct关键字
    Person p2;//c++中使用时候可以不用写struct关键字       
}
//5.bool类型增强 C语言中没有bool类型
bool flag = true;
void test(){
    cout<<flag<<endl;//1
	flag =100;
    //bool类型 真-非0转为1  假-0 
    cout<<flag<<endl;//1
}
//6.三目运算符增强
void test(){
    int a =10;
    int b = 20;
    cout<<"ret="<<(a>b?a:b)<<endl;//20
    (a>b?a:b)=100;//C++返回的是变量
    cout<<"a="<<a<<endl;//10
    cout<<"b="<<b<<endl;//100 
    //c语言中想模仿C++写
    *(a>b?&a:&b) = 100 //*取地址值    
}

//7.const增强 C++
const int a=10;//只读属性 //受到保护,不可以修改
void test(){
    const int b = 20 //C语言中是伪常量 c++中是真常量
    //C语言中可以修改局部B的值 C++中不可以修改
    int *p = (int *)&b;
    *p = 200
    printf("*p=%d,b=%d",*p,b)
    //int arr[b] C语言中不可以初始化数组
    
}




C语言const默认外部链接
//C语言const默认外部链接
//文件 test.c
//extern const int a = 10; C++语言外部链接需要添加extern
const int a = 10;//C语言const默认外部链接

int main(){
    extern const int a;//告诉编译器去外部查找这个
    print("a=%d\n",a)
}

c++中

//const int a = 10;默认是内部链接
//C++语言使用外部链接,需要添加extern提高作用域
extern const int a = 10; 
int main(){
    extern const int a;//告诉编译器去外部查找这个
    cout<<a<<endl;
}
C++语言const分配内存
//1、const分配内存,取地址会分配临时内存
//2、extern 编译器会给const变量分配内存
void test01(){
    const int a = 10;
    int *p=(int*)&a;//分配临时内存 int temp = a; int *p=(int*)&temp;
}

//3、使用变量初始化const的变量
void test02(){
    int a = 1;
    const int b = a;//会分配内存
    //分配内存的变量都可以使用指针取修改它
    int *p=(int*)&b;
    
}
//自定义数据类型 (string) 加const会分配内存
struct Person
{
    string m_Name;
    int m_Age;
}
void test03(){
    const Person p1;
    //p1.m_Name = "a" 不可以修改
    Person *p = (Person*)&p1;//指针可以修改
    p->m_Name = "";
    p->m_Age = 10;
    cout<<"m_Age:"<<m_Age<<"m_Name"<<m_Name<<endl;     
}



C++编程规范

全局变量:g_

成员变量:m_

静态变量:s_

常量:c_

驼峰标识

双冒号::

作用域运算符

int atk =200;
void test(){
	int atk = 100;
    cout<<"局部"<<atk<<endl;
    cout<<<"全局"<::atk<<endl;//全局作用域  ::
}

语法须知
/*
源文件扩展名:cpp
入口:main函数
java必须先有类,再有方法(函数)
 /* . */ 左边只能是对象
//为什么内存里面的值默认的都是ccccccc -858993460
中断:interrupt
cc->int3:起到断点的作用,防止代码执行到函数的时候出现乱码
*/

#include <iostream>
using namespace std;
struct Person{
    int m_age;
};
struct Student:Person{
    int m_no;
};


int main()
{
    Student student;
    
    cout<<""<<endl;
    return 0;
}


C++编程规范

全局变量:g_

成员变量:m_

静态变量:s_

常量:c_

驼峰标识

Debug模式

很多调试信息,生成可执行文件比较臃肿

Release模式

去除调试信息,生成可执行文件比较精简、高效

会优化代码

在release模式下,右击属性》禁止优化

发布项目使用这个模式

内存空间的布局

每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域

应用栈空间堆空间代码区全局区
QQ
微信
浏览器
栈空间

每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕之后会自动回收这段栈空间,用于存放局部变量

自动分配和回收

堆空间

需要主动申请和释放

在程序运行的过程中,能够自由控制内存的生命周期,大小

代码段/代码区:

用于存放代码

数据段/全局区:

用于存放全局变量,整个程序运行都存在

命名空间
//MJ 命名空间
namespace MJ{
    int g_age;
    void func(){
        
    }
    
    class Person{
        int m_age;
        int m_money;
    }
    
}

int main(){
    using namespace MJ;
    //从这里开始往下 都可以直接使用 MJ空间下的成员,可以省略MJ::
    g_age = 20;
    
}


#include <iostream>
using namespace std;

//命名空间嵌套
namespace AA {
    int g_age;
    void func() {
        cout << "这是AA" << endl;
    }
    namespace BB {
        namespace CC {
            int g_age;
        }
    }
}

void func() {
    cout << "这是全局" << endl;
}

int main() {
    using namespace AA;
    ::func();//调用全局的func

    ::AA::func();//调用AA的func
    
    //方式1
    AA::BB::CC::g_age = 20;
    AA::g_age = 30;

    //方式2
    using namespace AA;
    using namespace AA::BB::CC;
    AA::g_age = 40;
    //AA::BB::g_age = 40;//AA::BB::下没有g_age成员 报错
    AA::BB::CC::g_age = 40;

    //方式3
    using AA::BB::CC::g_age;
    g_age = 50;

    return 0;


}
//命名空间合并
namespace CC {
            int g_age;
        }
namespace CC {
            int g_name;
        }
//等价于
namespace CC {
            int g_age;
    int g_name;
        }

Extern
Extern
被extern “C” 修饰的代码会按照C语言的方式去编译
用于C和C++混合开发
#include <iostream>
using namespace std;
//extern "C" void func() {
//
//}
//extern "C" void func(int v1) {
//
//}

//extern "C" {
//	void func() {
//
//	}
// void func(int v1){
// 
// }
//}


//extern "C" {
//	void func();
//	void func(int v);
//}
//以上会报错 c语言不允许重载

//不会报错 ,这个不属于重载,属于不同的函数
//void func();
//extern "C" void func(int v);

extern "C" void func();
extern "C" void func(int v);

int main() {

	

	return 0;
}

void func() {
	cout << "func()" << endl;
}

void func(int v) {
	cout << "func(int)" << endl;
}
Extern“C”应用C和C++混合开发
名称解释
头文件专门用来声明函数的文件
源文件专门用来实现声明的文件
引入头文件#include “文件名.h”;
宏定义它的作用是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常用来定义常量。
一般宏
#define 宏名 字符串 比如:#define PI 3.14


#undef PI
PI在#undef是无效的
特殊宏#define __MATH_H
#pragma once防止头文件的内容被重复包含、兼容性不好、老编译器不支持,只针对整个文件
#ifndef 、#define、#endif防止头文件的内容被重复包含、不受任何编译器的限制,可以针对某段代码
#include <iostream>
using namespace std;
/*
	第三方库:C语言写的库
*/
//引用头文件
#include "math.h";
#include "test.h";

int main() {
	cout << sum(10, 20) << endl;
	cout << delta(10, 20) << endl;
	cout << divide(10, 0) << endl;
	test();
	return 0;
}

c++头文件

//test.h
void test();

c++文件

//test.cpp
//引入库
#include <iostream>
using namespace std;

#include "math.h";

void test() {
	cout<<"test():"<<sum(20,20)<<endl;
}

C语言文件

//math.c
//引入C语言头文件stdio.h
#include <stdio.h>

int sum(int v1, int v2) {
	return v1 + v2;
}

int delta(int v1, int v2) {
	return v1 - v2;
}

int divide(int v1, int v2) {
	if (!v2)
	{
		printf("v2不能为%d",v2);
		return v2;
	}
	return v1 / v2;
}

c语言头文件
//math.h
//宏定义
//如果没有定义__MATH_H 就定义 __MATH_H
#ifndef __MATH_H 
#define __MATH_H 
#ifdef __cplusplus
extern "C" {
#endif 

	int sum(int v1, int v2);
	int delta(int v1, int v2);
	int divide(int v1, int v2);

#ifdef __cplusplus	
}
#endif 
#endif
自定义类型typedef
//给类型起别名,最好使用typedef
typedef int Area;
using Sum = int;
Area a;
Sum b;

#include <stdio.h>
typedef char *String;
 int main(int argc, const char * argv[]) {
     // 相当于char *str = "This is a string!";
    String str = "This is a string!";
     
     printf("%s", str);
     
     return 0;
}

//typedef与#define
typedef char *String1;
#define String2 char *

int main(int argc, const char * argv[]) {
      String1 str1, str2;
     
      String2 str3, str4;
      return 0;
 }
//在这种情况下,只有str1、str2、str3才是指向char类型的指针变量,str4只是个char类型的变量。
类型转换-显示转换
//**类型(表达式);**
int(z);

//**(类型)表达式;**
(int)z;

//**类型转换操作符<类型>(表达式**)
static_cast<int>(z);

类型转换操作符:const_cast、dynamic_cast、reinterpret_cast、static_cast

#include <iostream>
using namespace std;
//定义枚举类型
enum GameResult{WIN,LOSE,TIE,CANCEL};
int main(){
    GameResult result;
    enum GameResult omit = CANCEL;
    for(int count = WIN;count<=CANCEL;count++){
        result = GameResult(count);
        if(result == omit)
            cout<<"The game was cancelled" << endl;
        else{
            count<<"The game was played!!";
            if(result == WIN) cout<<"and we won!";
            if(result == LOSE) cout<<"and we lost!";
            cout<<endl;
        }
    }
    return 0;
}

auto类型和decltype类型

auto:编译器通过初始值自动推断变量类型

decltype:定义一个变量与某个表达式的类型相同,但并不使用改表达式的初始化变量的值

输入输出
操纵符含义
<<预定义的插入符,作用在流类对象cout上便可以实现向标准输出设备输出;
cout<<表达式<<表达式…
>>标准输入是将提取符作用在流类对象cin上
cin>>表达式>>表达式
dec数值采用十进制表示
hex数值采用16进制表示
oct数值采用8进制表示
ws提取空白符
endl插入换行符,并刷新流
ends插入空字符
setsprecision(int)设置浮点数的小数位数(包括小数点)
setw(int)设置域宽
cout<<setw(5)<<setprecision(3)<<3.1415;
指针

指针里面存的是个内存地址

数组是一个指针

没有被赋值的指针是一个野指针,野指针容易导致程序崩溃

对野指针赋值是不允许的,比如:*p = 10;

取地址值

&变量名

声明指针

数据类型 * 指针名 = 某

个地址 ;

取指针指向的值

*指针名

指针赋值
指针1 = 指针2;这两个指针指向指针2存储的同一个地址
数组1 = 指针2;
指针遍历数组元素
int arr[5];
int *p = &arr[0];//指针指向第一个数组的地址
p+1;//arr[1] 往后推移一个元素 int类型向后移动4个字节 char往后移动是1个字节
for(int i;i<5;i++)
{
    //打印p+1
    print(p);
    p++;//
}
指针动态申请内存空间 / 释放内存空间
int *p = new int;//编译器会开辟出一个内存空间
*p = 10;
delete p;//删除内存/释放内存/系统回收内存,p又恢复了野指针
p = null;//解决野指针,让p指向空
//如果再次使用new语句之后,之前的空间没有使用delet,则之前的空间就会一直占用
p  = new int;

所以要记得释放内存

数组长度不能是变量
int len;
len = 2;
int arr[len]//报错:这种形式声明的数组,长度是不能动态赋值
指针动态申请数组长度
int len;
len = 2;
int *arr = new int[len];//建立数组指针,一次性申请len个内存,就是数组
//通过下标可以访问数组的各个成员
for(int i = 0;i<len;i++){
    arr[i] = i+1;
}
delete []p;
p = null;
指针的指针
int len =3;
int**arr = new int*[len];//创建二维指针
//可以通过arr[i] = new int[n];创建一维指针
for(int i = 0;i<row;i++)
{
    arr[i] = new int[col];//创建一维指针
}
//释放内存
//先释放一维指针
for(int i=0;i<row;i++)
{
   delete []arr[i]; 
}
//再释放二维
delete []arr;

引用

赋值不会建立引用关系,只有初始化才会;

short n = 10;//引用与被引用只要一方发生变化,另一个也会发生变化
short&m = n;//初始化只能是变量,
short x = 15;//引用只有在初始化的时候起作用
m = x;//赋值的话,m没有成为x的引用
m+=5;//
//m=20 n=20 x=15;

指向函数的指针
void func1(int a,void(*p)(int)){
    cout<<"func1:"<<endl;
    p(a);
}

void test(int a){
    cout<<"test(int)"<<a<<endl;
}

int main() {
    //指针指向函数: 返回类型 (*p)(参数类型1,参数类型2) = 函数名; 调用 p(参数1,参数2);
	void (*p)(int) = test;
    p(10);
    func1(10,test);
	cout << sum(10) << endl;
	cout << sum(10, 20,30) << endl;

	return 0;
}
引用(reference)

引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针

//指针相当于给age起了一个 别名:*p
int age = 10;
int height = 30;

int *p = &age;
*p = 20;//相当于age = 20;
*p = height//age = height;
p = &height;//指针可以更改指向    
    
cout<<"age="<<age<<endl;
    
//引用相当于给age起了一个 别名:ref
int &ref = age;//必须初始化的同时指定要引用的变量
/*
int &ref; 
ref = age;//这里既是赋值、又是指向引用,所以这样存在二义性,编译器直接提示报错
*/
ref = 40;//相当于age = 40;
ref = height;//age = height;
&ref = height;//引用不可以更改指向,这里编译器直接提示报错,

cout<<"age="<<age<<endl;

const

被const修饰的变量不可修改

如果修饰的是类、结构体(的指针),其成员不可以更改

#include <iostream>
using namespace std;
str

int main() {

	const int age = 20;
	age = 39;
	return 0;
}

const修饰的其右边的内容不可更改

//const修饰的其右边的内容
#include <iostream>
using namespace std;

struct Date
{
	int year;
	int month;
	int day;
};
int main() {

	Date d1 = { 2020,2,31 };
	Date d2 = { 2020,2,3 };

	const Date* p = &d1;
	Date* const q = &d1;
	cout << p->day << endl;
	/*p->day = 20;
	(*p).day = 30;*/
	p = &d2;
	//p1 = &d2;
	(*q).day = 10;
	cout << p->day << endl;
	cout << q->day << endl;
	(*p).day = 30;//报错 指针指向的内容不可以更改

	int age;
	int height;
    
	//p1不是常量,*p1是常量
	const int* p1 = &age;
	//p2不是常量,*p2是常量
	int const* p2 = &age;
	*p1 = 29;//报错  指针指向的内容不可以更改
	p1 = &height;
	*p2 = 23;//报错 指针指向的内容不可以更改
    
	//p3是常量,*p3不是常量
	int* const p3 = &age;
	*p3 = 30;
	p3 = &height;//报错 指针指向容不可以更改
	*p3 = 39;
    
	//p4是常量,*p4是常量
	const int* const p4 = &age;
	//p5是常量,*p5是常量
	int const* const p5 = &age;

	*p4 = 30;//报错 指针指向的内容不可以更改
	p4 = &height;//报错 指针指向容不可以更改 
	p5 = &height;//报错 指针指向容不可以更改 
	*p5 = 39;//报错 指针指向的内容不可以更改


	return 0;
}
const引用

引用可以被const修饰,这样就无法通过引用修改数据了,但可以访问,可以成为常引用

const引用特点

可以指向临时数据(常量、表达式、函数返回值等)

//常量
int age = 10;
int &ref = 30//报错
const int &ref = 30//加上个const就可以了
//表达式
int a = 1;
int b = 2;
int &c = a+b;//报错
const int &c = a+b//加上个const就可以了

可以指向不同类型的数据

//表达式
double a = 1;
const int &ref = a;//报错

作为函数参数时(此规则也适用于const指针)

  • 可以跟非const引用构成重载
  • 可以接受const和非const实参(非const引用,只能接受非const实参)
int	sum(int &v1,int &v2){
    return v1+v2;
}

int main(){
    int a = 10;
    int b = 20;
    sum(a,b);//非const实参
    sum(20,10);//常量实参 报错
    
    
    return 0;
}
//使用const引用,就可以接受常量实参 
int	sum(const int &v1,const int &v2){
    return v1+v2;
}

int main(){
    
    int a = 10;
    int b = 20;
    
    const int c = 10;
    const int d = 20;
    //非const实参
    sum(a,b);//非const实参
    sum(20,10);//常量实参     
    //const实参
    sum(c,d);
    
    return 0;
}
枚举
//枚举类型 
//不限定作用域的枚举类型
enum Weekday
{SUN,MON,TUE,WED,TUE,THU,FRI,SAT};
//默认值 0 1 2 3...
//1.先定义枚举类型,再定义枚举变量
enum Season {spring, summer, autumn, winter};
enum Season s;

//2.定义枚举类型的同时定义枚举变量
enum Season {spring, summer, autumn, winter} s;

//3.省略枚举名称,直接定义枚举变量
enum {spring, summer, autumn, winter} s;
字符串
#include <iostream>
using namespace std;

int main()
{
    //字符串 方式1
    const char* name = "bmw";
    //字符串 方式2
    char name2[] = {'b','m','w','\0'};//必须以\0 结尾,这是终止符
    //字符串长度 比字符个数 多1
    cout << strlen(name2) << endl;
    //数组名称 也是个指针,值等于首地址  
    char *p1 = name2;
    char *p2 = &name2[0];//name2 的首地址 即name[0]的地址  
    
    
    return 0;
}

面向对象

声明和实现分离
//头文件
#pragma once
class Person
{
    int m_age;
public:
    void setAge(int age);
    int getAge();
    Person();
    ~Person();
};


//实现
#include "Person.h"
void Person::setAge(int age) {

}

int Person::getAge() {
    return m_age;
}

Person::Person() {
    m_age = 0;
}

Person::~Person() {

}
//引入Person.h 头文件
#include <iostream>
using namespace std;

#include "Person.h"

int main()
{
    Person person;
    person.setAge(20);
    person.getAge();

    
    return 0;
}
结构体

当一个整体由多个数据构成时,我们可以用数组来表示这个整体,但是数组有个特点:内部的每一个元素都必须是相同类型的数据。

在实际应用中,我们通常需要由不同类型的数据来构成一个整体,比如学生这个整体可以由姓名、年龄、身高等数据构成,这些数据都具有不同的类型,姓名可以是字符串类型,年龄可以是整型,身高可以是浮点型。

为此,C语言专门提供了一种构造类型来解决上述问题,这就是结构体,它允许内部的元素是不同类型的。

结构体的声明和初始化
/*
struct 结构体名{
 	类型名1 成员名1;
	类型名2 成员名2;
	...
 	类型名n 成员名n;   
};

*/
//结构体内部的元素,也就是组成成分,我们一般称为"成员"。
//struct是关键字,是结构体类型的标志。
struct Student {
    char *name; // 姓名
    int age; // 年龄
    float height; // 身高
};
//初始化
struct Student stu = {"MJ", 27};

//前面只是定义了名字为Student的结构体类型,并非定义了一个结构体变量,就像int一样,只是一种类型。

//定义结构体变量方式:struct 结构体名 变量;
struct Student stu;
stu = {"MJ", 27};
cout << stu.name << endl;

结构体的访问
stu.age = 27;// 访问stu的age成员
指向结构体的指针
/*
结构体指针变量的定义形式:
    struct 结构体名称 *指针变量名
    有了指向结构体的指针,那么就有3种访问结构体成员的方式
    结构体变量名.成员名
    (*指针变量名).成员名
    指针变量名->成员名
*/
int main(int argc, const char *argv[]) {
     // 定义一个结构体类型
    struct Student {
        char *name;
         int age;
     };      
     struct Student stu = {"MJ", 27}; // 定义一个结构体变量 & 
     struct Student *p; //定义一个指向结构体的指针变量   
     p = &stu;  // 指向结构体变量stu
    //指针指向对象
     //这时候可以用3种方式访问结构体的成员
     // 方式1:结构体变量名.成员名
     printf("name=%s, age = %d \n", stu.name, stu.age);     
     // 方式2:(*指针变量名).成员名
     printf("name=%s, age = %d \n", (*p).name, (*p).age);     
     // 方式3:指针变量名->成员名
     printf("name=%s, age = %d \n", p->name, p->age);     
     return 0; 
}
//类和结构体的区别就是:类默认的访问权限是private,结构体默认的访问权限是public
//类
class Mj{
    private://默认
    int m_age;
    void run(){}
}
//结构体
struct Mj{//默认
    public:
    int m_age;
    void run(){}
}
申请堆空间
malloc申请空间

你想把它当做int 还是 char 取决于你,

//x86环境中
//申请4个字节空间,你想把它当做int 还是 char 取决于你
int *p = (int *)malloc(4);//返回首字节地址,使用指针接收 需要强转
*p = 10;
//回收free()
free(p);//只能回收一次

//char类型只占一个字节,
char *p = (char *)malloc(4);//返回首字节地址,使用指针接收
*p = 10;//只赋值了一个字节 相当于 p[0] = 10;
*(p+1) = 11;//p[1] = 11;
*(p+2) = 12;//p[2] = 12;
*(p+3) = 13;//p[3] = 13;

//回收free()
free(p);//只能回收一次 不会调用析构函数

new申请空间

你想把它当做int 还是 char 取决于你

int *p = new int;
*p = 10;

delete p;

//申请一个字节
char *p = new char;
*p = 10;

delete p;


//char类型数组 
char *p = new char[4];//申请4个字节
delete[] p;





申请空间初始化
int size = sizeof(int) * 10;
int *p = (int *)malloc(size);
//mymory set
//将每个字节设置为0
memset(p,0,size);
//memset(p,1,4);
// 00000001 00000001 00000001 00000001

int *p1 = new int;
int *p2 = new int();//初始化为0 带有小括号 会调用memset
int *p3 = new int(5);//初始化为5 带有小括号 会调用memset
int *p4 = new int[3];//申请3个int类型大小的空间,未初始化
int *p5 = new int[3]();//申请3个int类型大小的空间,初始化为0 带有小括号 会调用memset
int *p6 = new int[3]{};//申请3个int类型大小的空间,初始化为0 带有大括号 会调用memset
int *p7 = new int[3]{5};//申请3个int类型大小的空间,第一个元素被初始化为5,其它元素被初始化为0 带有大括号 会调用memset

//带有小括号 会调用memset

访问权限

成员访问权限、继承有3种方式

public:公共的,任何地方都可以访问(struct默认)

protected:子类内部,当前类内部可以访问

private:私有的,当前类内部可以访问(class默认)

#include <iostream>
using namespace std;

struct Person
{
public:
	int m_age;
private:
    int m_name;
};
//这种方式实际开发中不会这么用,这样会很乱,GoodStudent就不会继承到 m_age
struct Student:private Person
{
	//以私有的方式继承Person
	/*
	相当于
	private:
	int m_age;
	*/

};
//子类访问父级成员变量的权限,取决于
//父类本身的访问权限和上一级父类的继承方式
struct GoodStudent :public Student {
	void work() {
		m_age = 20;//报错
	}
};
//public是最常用的继承方式


int main()
{

}
struct默认public继承
#include <iostream>
using namespace std;

struct Person{
private:
	int m_age;
public:
	int getAge() {
		return m_age;
	}

};

struct Student:public Person{


};
//继承方式不影响GoodStudent的大小
struct GoodStudent : Student {
	void work() {
		//m_age = 20;
		int m_age = getAge();
	}
};



int main()
{
	//继承方式不影响GoodStudent的大小
	GoodStudent sd;
	cout << sizeof(sd) << endl;
}


class默认private继承
#include <iostream>
using namespace std;

class Person{

	int m_age;
	public:
	int getAge() {
		return m_age;
	}

};

class Student:public Person{


};
//继承方式不影响GoodStudent的大小
class GoodStudent :public Student {
	void work() {
		//m_age = 20;
		int m_age = getAge();
	}
};



int main()
{
	//继承方式不影响GoodStudent的大小
	GoodStudent sd;
	cout << sizeof(sd) << endl;
}
对象
对象的内存可以存放3个地方
全局区全局变量
堆空间动态申请(malloc、new等)
栈空间函数里面的局部变量
//结构体
struct Mj{//默认
   //成员变量 
    int m_age;
    int m_name;
    //成员方法
    void run(){}
}

int main(){
     Mj mj; // 定义一个结构体变量 & 
     Student *p; //定义一个指向结构体的指针变量   
     p = &mj;  // 指向结构体变量stu
    //指针指向对象
     //这时候可以用3种方式访问结构体的成员
     // 方式1:结构体变量名.成员名
     printf("name=%s, age = %d \n", mj.m_name, mj.m_age);     
     // 方式2:(*指针变量名).成员名
     printf("name=%s, age = %d \n", (*p).m_name, (*p).m_age);     
     // 方式3:指针变量名->成员名
     printf("name=%s, age = %d \n", p->m_name, p->m_age); 
}

类的成员变量内存布局
struct Person(){
    int m_id;
    int m_age;
    int m_height;
    void run(){}
}
int main(){
    Person person;
    person.m_id = 1;
    person.m_age = 2;
    person.m_height = 3;
}
内存地址内存数据
&person&person.m_id0x00E69B601
0x00E69B61
0x00E69B62
0x00E69B63
&person.m_age0x00E69B642
0x00E69B65
0x00E69B66
0x00E69B67
&person.m_height0x00E69B683
0x00E69B69
0x00E69B6A
0x00E69B6B
THIS
#include <iostream>
using namespace std;
struct Person {
    int m_age;

    /*
    * 能够访问成员变量的方式 
      默认会把 person1对象的地址值传过来,Person *person = &person1;
      this -> person 
    void run(Person *person) {
        cout << "Person::run() - " << person->m_age << endl;
    }*/
    
    void run() {
        //this 指针存储这函数调用者的地址
        //this 指向了函数调用者
        //this = &person1;
        //this->m_age;
        cout << "Person::run() - " <<  this->m_age << endl;
    }
};

int main()
{
    Person person1;//栈空间
    person1.m_age = 20;
    person1.run();//代码区

    Person person2;
    person2.m_age = 40;
    person2.run();

    return 0;
}

//栈空间
//堆空间
//代码区
//全局区
内联函数
#include <iostream>
using namespace std;
/*内联函数:将函数调用直接展开为函数代码*/
inline void func();
inline int sum(int v1, int v2);
//开辟栈空间
void func() {
	cout << "func()1" << endl;
	cout << "func()2" << endl;
	cout << "func()3" << endl;
}
//回收栈空间

//调用频繁、代码体积少,适合使用内联函数
//尽量不要超过10行
inline int sum(int v1, int v2) {
	return	v1 + v2;
}
//递推不会变成内敛 死循环
inline void run() {
	run(); 
}

int main() {
	
	func();
	func();
	func();

	int c = sum(10, 29);//不会开辟栈空间
	int c = 10+29;

	return 0;
}

函数重载
#include <iostream>
using namespace std;
//函数的重载  name mangling/name decoration 底层技术
//C语言没有函数重载

int sum(int v1, int v2) {
	return v1 + v2;
}

int sum(int v1, int v2,int v3) {
	return v1 + v2+v3;
}

//返回值类型相同不能构成重载 --歧义/二义性
//int sum(double v1) {
//
//}

//实参的隐式转换可能产生二义性
//使用重载name mangling技术编译之后变成:display_long
void display(long a) {

}
//使用重载name mangling技术编译之后变成:display_double
void display(double a) {

}
//使用重载name mangling技术编译之后变成:display_int
void display(int a) {

}

int main() {

	cout << sum(10, 20) << endl;
	cout << sum(10, 20, 30) << endl;
	//display(10); 产生二义性 

	return 0;
}

//在release模式下,右击属性》禁止优化 可以看到函数重载的技术原理
默认参数
#include <iostream>
using namespace std;

int age = 20;
//默认参数 必须是从右到左
void func(int v1,int v2,int v3 = 20,int v4)//报错
//如果函数同时有声明、实现、默认参数只能放在函数声明中  
//默认参数的值可以是常量、全局符号(全局变量、函数名)
int sum(int v1, int v2 = 20) {
	return v1 + v2;
}
int sum(int v1, int v2,double v3 = age) {
	return v1 + v2 + v3;
}
void func1(int a,void(*p)(int)){
    cout<<"func1:"<<endl;
    p(a);
}

void test(int a){
    cout<<"test(int)"<<a<<endl;
}

int main() {
    //指针指向函数
	void (*p)(int) = test;
    p(10);
    func1(10,test);
	cout << sum(10) << endl;
	cout << sum(10, 20,30) << endl;

	return 0;
}

函数重载。默认参数可能会产生冲突、二义性(建议优先选择使用默认参数)

int sum(int v1) {
	return v1;
}
int sum(int v1, int v2 = 20) {
	return v1 + v2;
}
int main() {
	sum(10);//这里会报错
	return 0;
}
初始化列表
#include <iostream>
using namespace std;

struct Person
{
    
    int m_age;
    int m_height;
    /*Person(int age,int height) {
        m_age = age;
        m_height = height;
    }*/


    /*Person(int age, int height) : m_age(age),m_height(height)
    {
        
    }*/

    int func() {
        return 0;
    }

    int f_age() {
        cout << " f_age()" << endl;
        return 0;
    }

    int f_height() {
        cout << " f_height()" << endl;
        return 0;
    }
    //初始化顺序和成员变量定义的顺序保持一致
    Person(int age, int height) : m_age(f_age()), m_height(f_height())
    {
        //输出结果
		// f_age()
        // f_height()
    }
};


int main()
{
    Person person0(23,180);
    

    cout << person0.m_age << endl;
    cout << person0.m_height << endl;

}

如果声明和实现分离

初始化列表只能写在实现中

默认参数只能写在声明中

#include <iostream>
using namespace std;
struct Person
{
    
    int m_age;
    int m_height;
    int m_id;
    //声明
    Person(int age = 0, int height = 0, int id = 0);
};
//实现
Person::Person(int age, int height, int id) : m_age(age), m_height(height), m_id(id)
{
    m_age = 10;
}

int main()
{
    Person person;
    Person person0(180);
    Person person1(23,180);
    Person person2(23,180,23);
    

    cout << person0.m_age << endl;
    cout << person0.m_height << endl;

}

构造函数
  • 构造函数也叫构造器,在对象创建的时候自动调用,一般用于完成对象的初始化工作
  • 新的对象诞生的象征
  • 必须是public 才可以让外界访问
#include <iostream>
using namespace std;

//特点 函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数
//一旦定义了构造函数,必须使用其中一个自定义的构造函数来初始化对象
struct Person {
    int m_age;
    int m_name;
    int m_height;

    //构造函数 
    //没有返回值 自动调用
    Person() {
         cout << sizeof(this) << endl;//4 
        memset(this, 0, sizeof((*this)));//*this是操作this指向的对象 是person
        cout << "Person()\n" << m_age<<endl;
        cout << "Person()\n" << m_name <<endl;
        cout << "Person()\n" << m_height <<endl;
    }
    //构造函数的重载
    Person(int age) {
        memset(this, 0, sizeof(this));
        cout << "Person(int age)\n";
    }
};

int main()
{ 
    Person person;
    //person.m_age = 2;
    cout << sizeof(person) << endl;//12
    Person* p = &person;//栈内存:p是指针变量,所以在栈内存
    cout << sizeof(p) << endl;//4
    
    //通过new创建 会调用构造函数
    Person* p1 = new Person;//堆内存,通过new关键字出来的都是在堆内存
    p1->m_age = 20;
    delete p1;

    //通过malloc创建 不会调用构造函数
    Person* p2 = (Person*)malloc(sizeof(Person));
    free(p2);    
    //对象和指针访问方式的不同
    Person person5;
    Person *person6 = new Person;
    person5.m_age = 20;//对象 使用 点
    person6->m_age = 20;//指针 使用 箭头
}

注意:通过malloc申请的空间不会调用构造函数

C++默认构造函数误区

只有再特定情况下,才会调用默认构造函数

https://www.bilibili.com/video/BV1Lo4y1o717?p=49

//不会调用默认构造函数的情况
#include <iostream>
using namespace std;
struct Person {
    int m_age;
   
};

int main()
{
    Person person5;
    person5.m_age = 10;
}
//调用默认构造函数的情况
#include <iostream>
using namespace std;
struct Person {
    int m_age = 10;//区别在这里
   
};

int main()
{
    Person person5;
    person5.m_age = 10;
}
成员变量初始化

如果有自定义构造函数,则除了全局对象,其它的对象都不会初始化

https://www.bilibili.com/video/BV1Lo4y1o717?p=51

//有自定义构造函数
#include <iostream>
using namespace std;
struct Person {
    int m_age;
    Person() {
       // memset(this, 0, sizeof(Person));//全部初始化 成员变量为0
    }
    Person(int age) {
       // memset(this, 0, sizeof(Person));//全部初始化 成员变量为0
    }

};

Person g_person0;//成员变量都初始化0
Person g_person1();//函数声明
Person g_person2(10);//

int main()
{

    //如果有自定义构造函数,则除了全局对象,其它的对象都不会初始化
    //栈空间 
    Person person5;//成员变量不会初始化

     //堆空间
     //成员变量都不会初始化
    Person* person6 = new Person;
    Person* person7 = new Person();
    Person* person8 = new Person[3];
    Person* person9 = new Person[3]();//3个Person对象 的成员变量都不会初始化
    Person* person10 = new Person[3]{};//3个Person对象 的成员变量都不会初始化
    //打印结果---是否初始化为0
    cout << g_person0.m_age << endl;//0 是
    cout << g_person2.m_age << endl;//0 是
    cout << person6->m_age << endl;//842150451 否
    cout << person7->m_age << endl;//842150451 否
    cout << (*(person8)).m_age << endl;//842150451 否
    cout << (*(person8 + 1)).m_age << endl;//842150451 否
    cout << (*(person9)).m_age << endl;//842150451 否
    cout << (*(person9 + 1)).m_age << endl;//842150451 否
    cout << (*(person10)).m_age << endl;//842150451 否
    cout << (*(person10 + 1)).m_age << endl;  //842150451 否
}

//没有自定义构造函数
#include <iostream>
using namespace std;
struct Person {
    int m_age;
       
};

Person g_person0;//成员变量都初始化0
Person g_person1();//函数声明

int main()
{
   //如果没有定义构造函数,并且没有初始化成员变量
    Person person0;//直接报错
    Person person1();//函数声明
   
   //栈空间 
     //前提:如果没有定义构造函数,直接报错 
    Person person5;//成员变量不会初始化
    
    //堆空间
    //前提:如果没有定义构造函数
    Person *person6 = new Person;//成员变量不会初始化
    Person *person7 = new Person();//成员变量都初始化0
    Person *person8 = new Person[3];//不会成员变量不会初始化
    Person *person9 = new Person[3]();//3个Person对象 的成员变量都初始化0
    Person* person10 = new Person[3]{};//3个Person对象 的成员变量都初始化0
	

}

构造函数互相调用

作用:优化代码,让重复的初始化操作简化

#include <iostream>
using namespace std;
struct Person
{
    int m_age;
    int m_height;
    //构造函数调用构造函数,必须在初始化列表里面调用
    Person() : Person(0,0){
        //创建了一个临时的Person对象
        //Person(20, 20); 返回一个person对象 
        //这样写,这个调用有参构造传给this的对象地址是一个临时的地址,并不是person的地址;所以初始化的成员变量不是person对象的成员变量。
        //等价于 => 以下写法
        //Person person; //这样创建对象默认不会调用构造函数,不会产生递归 https://www.bilibili.com/video/BV1Lo4y1o717?p=49
        //person.m_age = 20;
        //person.m_height = 20;
    }
    Person(int age, int height) {
        this->m_age = age;
        this->m_height = height;
    }

};

int main()
{
    Person person;
    cout << "person.m_age = " << person.m_age << endl;
    cout << "person.m_height = " << person.m_height << endl;

    return 0;
}

父类的构造函数

父类如果有无参构造函数和有参构造函数,子类的构造函数默认会调用父类的无参构造函数
父类如果没有无参的构造函数,但是有有参构造函数,就必须调用有参构造函数,否则报错
如果父类没有构造函数,就不会调用构造函数,汇编代码里面不会出现call Person::Person()

//方式1
#include <iostream>
using namespace std;
struct Person {
    int m_age;

    Person() {
        //m_age = 0;
        cout << "Person::Person()" << endl;
    }

    Person(int age) {
        cout << "Person::Person(int age)" << endl;
    }
};

struct Student :Person {
    int m_no;
    //父类如果有无参构造函数和有参构造函数,子类的构造函数默认会调用父类的无参构造函数    
    //父类如果没有无参的构造函数,但是有有参构造函数,就必须调用有参构造函数,否则报错
    //如果父类没有构造函数,就不会调用构造函数,汇编代码里面不会出现call Person::Person()
    Student():Person(10) {//子类调用父类的有参构造函数
        //call Person::Person() 
        cout << "Student::Student()" << endl;
    }
};


int main()
{
    Student student;

    cout << "" << endl;
    return 0;
}
//调用父类构造函数
//子类调用父类的构造函数,来初始化父类的成员变量
#include <iostream>
using namespace std;
class Person {
    int m_age;
public:
    Person(int age):m_age(age) {}
};

class Student :Person {
    int m_no;
public:
    //如何初始化age,需要调用父类构造函数
    Student(int age,int no) : m_no(no),Person(age) {}//调用父类构造函数
};


int main()
{
    Student student(23,23);

    return 0;
}


析构函数
  • 析构函数也叫析构器,在对象销毁的时候自动调用,一般用于完成对象的清理工作
  • 函数名以~开头,函数名与类同名,无返回值(void都不能写),无参数,不可以重载,有且只有一个析构函数
  • 一个对象销毁的象征
  • 通过malloc申请分配的空间所创建的对象,调用free的时候不会调用析构函数
  • 必须是public 才可以让外界访问
#include <iostream>
using namespace std;
struct Car
{
    Car() {
        cout << "Car::Car()" << endl;
    }
    ~Car() {
        cout << "Car::~Car()" << endl;
    }
};

struct Person
{
    int m_age;
    Car* m_car;

    Person() {
        m_age = 20;
        m_car = new Car;//在堆里面,不会自动销毁,对象内部申请的堆空间
        cout << "Person::Person()" << endl;
    }
    //专门用来做内存清理工作的
    ~Person() {
        delete m_car;//对象内部申请的堆空间,由对象内部回收
        cout << "Person::~Person()" << endl;
    }

};


int main()
{
    {
        Person *person = new Person ;//在堆里面,不会自动销毁
        delete person;
    }
    {
        Person person;
    }
    return 0;
}
//子类调用父类析构函数

#include <iostream>
using namespace std;

class Person {
    int m_age;
public:
    Person(int age):m_age(age) {}
    ~Person(){
        cout << "Person::~Person()" << endl;
    }
};
//
class Student :Person {
    int m_no;
public:
    Student(int age,int no) : m_no(no),Person(age) {}
    //先调用子类的析构函数
    ~Student() {
        /*
        
        .....
        */
        cout << "Student::~Student()" << endl;
        //再调用父类的析构函数
        //call Person::~Person();
    }
};


int main()
{
    {
        Student student(23, 23);
    }

    return 0;
}
继承
#include <iostream>

using namespace std;

//继承 

//父类 超类 superclass
struct Person
{
    int m_age;
    void run(){}
};
//子类、派生类 subclass
struct Student : Person
{
    int m_score;
    int no;
    void study() {

    }

};

struct GoodStudent:Student
{
    int m_money;
};

struct Worker : Person
{
    int m_salary;
    void work() {

    }
};


int main()
{
    Student student;
    student.m_age = 20;
    student.m_score = 90;
    student.run();
    student.study();

    GoodStudent gs;
    //内存布局:
    //gs 继承了 Person 和 Student 相当于把 Person和Student的成员直接放到GoodStudent中
    //struct GoodStudent :Student
    //{
    //    int m_age;
    //    int m_score;
    //    int no;
    //    int m_money;
    //	  方法是放在代码区的 
    //};

    cout << "Hello World!\n";
}

多继承

c++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)

#include <iostream>
using namespace std;

struct Student {
    int m_score;
    Student(int score):m_score(score){}
    void study() {
        cout << "Student::study()-m_score=" << m_score << endl;
    }

};

struct Worker {
    int m_salary;
    Worker(int salary) :m_salary(salary) {}
    void work() {
        cout << "Worker::work()- m_salary=" << m_salary << endl;
    }

};
//先继承Student 后继承Worker
//成员变量的顺序和继承顺序一直
struct Undergraduate : Student,  Worker {
    int m_grade;
    Undergraduate(int score,int salary,int grade) : Student(score), Worker(salary), m_grade(grade){
    
    }
    void play() {
        cout << "Undergraduate::play()-grade=" << m_grade << endl;
    }

};

int main()
{
    Undergraduate ug(99,4000,4);
    /*ug.m_grade = 4;
    ug.m_salary = 4000;
    ug.m_score = 99;*/
    ug.play();
    ug.study();
    ug.work();


    return 0;
}
封装
#include <iostream>
using namespace std;


struct Person{
private:
	int m_age;
public:
    void setAge(int age){
        if(age<=0) return;
        m_age = age;
    }
	int setAge() {
		return m_age;
	}
};

int main(){
    Person person;
    person.setAge(-4);
    person.setAge(29);
    cout<<person.getAge()<<endl;
}


多态

一个对象的多种形态;

多态的要素
  • 子类重写父类的成员函数(必须是虚函数)
  • 父类指针指向子类对象
  • 利用父类指针调用(重写的)成员函数
  • 找到指针对象所指向的存储空间,取出里面的前面四个字节(虚表的地址),通过虚表找到函数,取出对应的函数地址值
struct Person{
    int m_age;
}
struct Student:Person{
    int m_score;
}
//父类指针 指向 子类对象
Person* person = new Student(); //这里申请一个Student类型的堆空间,有成员变量m_age,m_score
//但是这里只能访问父类的成员变量,操作安全,因为这是Person类型的指针
person->m_age;//

//假设子类指针指向父类对象

//这里申请一个Person类型的堆空间,只有成员变量m_age
Student*student = (Student *)new Person();//进行强转 student类型,骗过编译器
student->m_age;
//则这里可以访问到m_score,因为这是Student类型的指针
student->m_score;//这里操作就很危险,因为这里访问了不属于自己的内存空间

//默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态

//多态是面向对象非常重要的一个特性
//同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
//在运行时,可以识别出真正的对象类型,调用对应子类中的函数



虚函数(C++实现多态的技术)
#include <iostream>
using namespace std;

struct Animal {
    //父类的函数是个虚函数,子类重写函数的也是个虚函数
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

struct Cat: Animal {
    //子类重写父类的成员函数
    void speak() {
        cout << "Cat::speak()" << endl;
    }
    void run() {
        cout << "Cat::run()" << endl;
    }

};

struct Dog : Animal {
    //重写(覆写、覆盖、override) 返回值 函数名 参数 都一样
    void speak() {
        cout << "Dog::speak()" << endl;
    }
    void run() {
        cout << "Dog::run()" << endl;
    }

};

struct Pig : Animal {
    
    void speak() {
        cout << "Pig::speak()" << endl;
    }
    void run() {
        cout << "Pig::run()" << endl;
    }

};
//父类指针指向子类对象
void Liu(Animal *animal) {
    animal->speak();//利用父类指针调用(重写的)成员函数
    animal->run();
    delete animal;
}

int main()
{
    Liu(new Dog());
    Liu(new Cat());
    Liu(new Pig());



    cout << "" << endl;
    return 0;
}
虚表
  • 没有虚函数的时候,汇编代码是直接调用父类的函数
  • 正常编程时,只要有虚函数就会有虚表,无论指针指向的是什么类型,对象里面都会存在虚表
  • 当重写所有的虚函数,会产生虚表(一个虚表占4个字节),调用函数的时候,就需要访问对象的前四个字节,前四个字节就是虚表,虚表里面存放是对象的函数的地址值
  • 多继承会产生多个虚表,有几个父类(前提:有虚函数)就有几个虚表
//在产生多态的时候,虚表的地址值就会存放在对象的内存的前4个字节空间中,当调用函数的时候
Liu(new Cat());//new Cat()申请堆空间,对象占12字节 前4个字节是虚表
//虚表的地址:0xB89B64
//虚表里面存放是对象的函数的地址值 
//内存地址值0xB89B64存放的是Cat::speak()的函数地址->0x00B814E7
//内存地址值0xB89B68存放的是Cat::run()的函数地址->0x00B814CE

原理:找到指针对象所指向的存储空间,取出里面的前面四个字节(虚表的地址),通过虚表找到函数,取出对应的函数地址值

//如果没有重写虚函数,虚表里面存放的地址还是父类原来的虚函数地址
#include <iostream>
using namespace std;

struct Animal {
    int m_age;
    //父类的函数是个虚函数,子类重写函数的也是个虚函数
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

struct Cat : Animal {
    //子类重写父类的成员函数
    int m_height;
    void run() {
        cout << "Cat::run()" << endl;
    }
    void speak() {
        cout << "Cat::speak()" << endl;
    }
    Cat() {
        cout << "Cat::Cat()" << endl;
    }
    ~Cat() {
        cout << "Cat::~Cat()" << endl;
    }

};
//父类指针指向子类对象
void Liu(Animal* animal) {
    animal->speak();//利用父类指针调用(重写的)成员函数
    animal->run();
    cout << sizeof(*animal) << endl;//8个字节 虚表+m_age
    delete animal;
}

int main()
{
    Cat* c = new Cat();//虚表+m_age+m_height
    Animal* a = new Cat();//虚表+m_age 虚表:存放的是 Cat::run() Cat::speak()
    Liu(c);
    new Cat();
    cout << sizeof(*(new Cat())) << endl;//12 虚表+m_age+m_height
    cout << sizeof(new Cat()) << endl;//4 new Cat()的地址
    cout << sizeof(Cat) << endl;//12 虚表+m_age+m_height
    cout << sizeof(*c) << endl;//12 虚表+m_age+m_height
    cout << sizeof(*a) << endl;//8 虚表+m_age
    //new Cat() 是个地址
   
    return 0;
}

  • 子类如果只重写了一个虚函数,虚表里面存在重写的子类函数地址和没有重写的父类虚函数地址
//如果没有重写虚函数,虚表里面存放的地址还是父类原来的虚函数地址
#include <iostream>
using namespace std;

struct Animal {
    //父类的函数是个虚函数,子类重写函数的也是个虚函数
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

struct Cat: Animal {
    //子类重写父类的成员函数
    
    void run() {
        cout << "Cat::run()" << endl;
    }

};
//父类指针指向子类对象
void Liu(Animal *animal) {
    animal->speak();//利用父类指针调用(重写的)成员函数
    animal->run();
    delete animal;
}

int main()
{
    Liu(new Cat());
    return 0;
}
//内存地址值0xB89B64存放的是Animal::speak()的函数地址->0x00B814E7
//内存地址值0xB89B68存放的是Cat::run()的函数地址->0x00B814CE


#include <iostream>
using namespace std;

/*

*/
class Person {
    int m_age;
public:
    Person() {

    }
    virtual void run() {
        cout << "Person" << endl;
    }
};

class Student :public Person{
public:
    int m_score = 100;
    /*编译器会在默认构造函数里面 把 m_score = 100;*/
    void run() {
        cout << "Student" << endl;
    }
};


int main()
{
    Person *person = new Student();

    cout<<sizeof(*person)<<endl;//8 = m_age + 虚表 
    cout<<sizeof(Student)<<endl;//12= m_age + m_score + 虚表
    
    Cat* c = new Cat();//虚表+m_age+m_height

    c->m_age = 1;
    c->m_height = 2;
    c->run(); 
    c->speak();
    c->run();
    cout << sizeof(Cat) << endl;
    //0x0067ED60
    //68 ab f4 00 00f4ab68
    /*
    72 11 f4 00  00F41172
    1e 10 f4 00  00F4101E
    00 00 00 00 
    43 61 74 3a  
    3a 72 75 6e 
 
    */

    Student student;
    return 0;

}

调用父类成员函数
struct Animal {
    //父类的函数是个虚函数,子类重写函数的也是个虚函数
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

struct Cat: Animal {
    //子类重写父类的成员函数
    
    void run() {
        Animal::run();//调用父类成员函数
        cout << "Cat::run()" << endl;
    }

};
虚析构函数
//当存在父类对象指向子类对象的时候,也就是多态的时候,需要把父类的析构函数声明为虚析构函数,如果不是,则delete cat的时候,则不会调用Cat的析构函数
struct Animal {
    //父类的函数是个虚函数,子类重写函数的也是个虚函数
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
    //构造函数
    Animal(){
        cout << "Animal::Animal()" << endl;
    }
    //虚-析构函数
    virtual ~Animal(){
        cout << "Animal::~Animal()" << endl;
    }
};

struct Cat: Animal {
    //子类重写父类的成员函数
    
    void run() {
        Animal::run();//调用父类成员函数
        cout << "Cat::run()" << endl;
    }
    //构造函数
    Cat(){
         //Call Animal::Animal
        cout << "Cat::Cat()" << endl;        
    }
    //析构函数
    ~Cat(){
        cout << "Cat::~Cat()" << endl;
        //Call Animal::~Animal
    }
};

int main(){
    Animal *cat = new Cat();
    cat->speak();
    cat->run();
    
    delete cat;
    
    return 0;
}

纯虚函数

没有函数体且初始化为0的虚函数,用来定义接口规范

抽象类(Abstract class)
  • 含有纯虚函数的类,不可以实例化(不可以创建对象)
  • 子类没有完全重写纯虚函数,那么这个子类依然是抽象类
#include <iostream>
using namespace std;

struct Animal {//抽象类
    //纯虚函数
    virtual void speak() = 0;
    virtual void run() = 0;
};

struct Cat : Animal {//抽象类--因为子类没有完全重写纯虚函数
    void run() {
        cout << "Cat::run()" << endl;
    }

};
//只要在KafeiCat之前的所有类中,加一起都重写了纯虚函数,那么KafeiCat就不是抽象类
//
struct KafeiCat : Cat {
    void run() {
        cout << "KafeiCat::run()" << endl;
    }
    void speak() {
        cout << "KafeiCat::speak()" << endl;
    }

};

int main()
{
    Animal anim;//报错 不可以创建对象
    Animal* kafeicat = new KafeiCat();// 报错 不可以创建对象
    kafeicat kafei;
    
    cout << "" << endl;
    return 0;
}
多继承-虚函数
#include <iostream>
using namespace std;

struct Student {
    int m_score;
    Student(int score):m_score(score){}
    virtual void study() {
        cout << "Student::study()-m_score=" << m_score << endl;
    }

};

struct Worker {
    int m_salary;
    Worker(int salary) :m_salary(salary) {}
    virtual void work() {
        cout << "Worker::work()- m_salary=" << m_salary << endl;
    }

};
//先继承Student 后继承Worker
//成员变量的顺序和继承顺序一直
struct Undergraduate : Student,  Worker {
    int m_grade;
    Undergraduate(int score,int salary,int grade) : Student(score), Worker(salary), m_grade(grade){
    
    }
    void play() {
        cout << "Undergraduate::play()-grade=" << m_grade << endl;
    }
    void study() {
        cout << "Undergraduate::study()-m_score=" << m_score << endl;
    }
    void work() {
        cout << "Undergraduate::work()- m_salary=" << m_salary << endl;
    }

};

int main()
{
    //工作者
    Worker* worker = new Undergraduate(33, 4000, 5);
    worker->work();

    //学生
    Student* student = new Undergraduate(22, 4000, 2);
    student->study();

    //工作和学生
    Undergraduate ug(99,4000,4);
    ug.play();
    ug.study();
    ug.work();
    
    return 0;
}
菱形继承
#include <iostream>
using namespace std;

struct Student {
    int m_score;
    Student(int score):m_score(score){}
    virtual void study() {
        cout << "Student::study()-m_score=" << m_score << endl;
    }

};

struct Worker {
    int m_salary;
    Worker(int salary) :m_salary(salary) {}
    virtual void work() {
        cout << "Worker::work()- m_salary=" << m_salary << endl;
    }

};
//先继承Student 后继承Worker
//成员变量的顺序和继承顺序一直
struct Undergraduate : Student,  Worker {
    int m_grade;
    Undergraduate(int score,int salary,int grade) : Student(score), Worker(salary), m_grade(grade){
    
    }
    void play() {
        cout << "Undergraduate::play()-grade=" << m_grade << endl;
    }
    void study() {
        cout << "Undergraduate::study()-m_score=" << m_score << endl;
    }
    void work() {
        cout << "Undergraduate::work()- m_salary=" << m_salary << endl;
    }

};

int main()
{
    //工作者
    Worker* worker = new Undergraduate(33, 4000, 5);
    worker->work();

    //学生
    Student* student = new Undergraduate(22, 4000, 2);
    student->study();

    //工作和学生
    Undergraduate ug(99,4000,4);
    ug.play();
    ug.study();
    ug.work();
    
    return 0;
}
虚继承

解决菱形继承基类成员变量二义性的问题

#include <iostream>
using namespace std;

struct Person {
    int m_age = 1;
};
//虚继承
struct Student :virtual Person {
    //int m_age = 1;
    int m_score = 2;
};

//虚继承
struct Worker :virtual Person {
    //int m_age = 1;
    int m_salary = 3;
};

struct Undergraduate : Student, Worker {
    /*
    如果没有虚继承,继承过来相当于这样:
    int m_age = 1;
    int m_score = 2;
    int m_age = 1;
    int m_salary = 3;    
    
    这时,m_age会被重复继承,如果有了虚继承:成员变量的空间布局
    Student类-虚表-偏移量 0 -20(从本类其实位置到基类成员变量的位置)
    int m_score
    Worker类-虚表-偏移量 0 -12(从本类其实位置到基类成员变量的位置)
    int m_salary
    int m_grade
    m_age //基类Person的m_age会被放到最下边 ,通过偏移量可以找到同一个m_age
    */
    int m_grade = 4;
};

int main()
{
    Undergraduate ug;
    ug.m_age = 10;

    return 0;
}
多继承应用
#include <iostream>
using namespace std;

//Java开发:接口
//iOS开发:协议
//抽象类
class JobBaomu {
public:
    virtual void clean() = 0;
    virtual void cook() = 0;
};
class JobTeacher {
public:
    virtual void playFootBall() = 0;
    virtual void playBaseBall() = 0;
};

class Student : public JobBaomu,public JobTeacher{
    int m_socre;
public:
    void clean() {

    }
    void cook() {

    }

};

class Worker : public JobTeacher {
    int m_salary;
public:
    void playFootBall() {

    }
    void playBaseBall() {

    }

};




int main()
{
    cout << "Hello World!\n";


    return 0;
}


static成员
  1. 静态成员:被static修饰的成员变量\函数
  2. 可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态成员);
  3. 静态成员变量:存储在数据段(全局区、类似于全局变量),整个程序运行过程中只有一份内存,对比全局变量,他可以设定访问权限(public、protected、private),达到局部共享的目的;
  4. 必须初始化、必须在类外边出事还好、初始化时不能带static、如果类的声明和实现分类(在实现.cpp中初始化)
  5. 静态成员函数:内部不能使用this指针(this指针只能用在 非静态 成员函数 内部),不能是虚函数(多态-父类指针指向子类对象,和类冲突),
  6. 不能访问非静态成员变量和非静态成员函数(和类访问冲突,不存在对象),静态成员函数内部只能访问静态成员
  7. 非静态成员函数可以访问静态成员函数和静态成员变量
#include <iostream>
using namespace std;

class Car {

    int m_age;
protected://Car和子类访问
    int m_score;
public:
    static int m_price;
    static void run() {
        cout << "Car::run()" << endl;
        //this 这里面是不可能有this
        //m_age = 0;  //没有对象也可以访问这个方法,没有对象哪来的成员对象
    }

};

int m_price;//谁都可以访问

//在类外边初始化成员变量
int Car::m_price = 0;

int main()
{
    Car::run(); //这里并没有创建对象,不存在对象
    Car car1;
    car1.m_price = 100;

    car1.run();
    

    Car car2;
    car2.m_price = 200;

    Car car3;
    car3.m_price = 300;
    //m_price 只存在一份,相当于全局变量
    cout << car1.m_price << endl;//300
    cout << car2.m_price << endl;//300
    cout << car3.m_price << endl;//300

    Car* car4 = new Car();
    car4->m_price = 400;

    cout << car4->m_price << endl;//400
    delete car4;

    cout << Car::m_price << endl;//400

    


}


单例模式
#include <iostream>
using namespace std;
/*
* 单例模式:设计模式的一种,保证某个类永远只创建一个对象
* 1.构造函数私有化
*/

class Rocket {  

private:
    static Rocket* ms_rocket;//Rocket保证对象唯一
    Rocket() {}//构造函数私有化,外界无法创建对象
    ~Rocket() {}//析构函数私有化,外界无法销毁对象
public:
       
    static Rocket *sharedRocket() {
        if (ms_rocket == NULL) {
            ms_rocket = new Rocket();
        }
        return ms_rocket;
    }

    void run() {
        cout << "Rocket::run()" << endl;
    }

    static void deleteRocket() {
        //这里考虑线程安全问题
        if (ms_rocket == NULL) {
            delete ms_rocket;
            ms_rocket = NULL;//不清空会产生野指针
        }
    }
};

Rocket* Rocket:: ms_rocket = NULL;

class RedRocket :public Rocket {
public:
    /*RedRocket() {

    }*/

};

int main()
{
    Rocket *p1 = Rocket::sharedRocket();
    Rocket *p2 = Rocket::sharedRocket();
    Rocket *p3 = Rocket::sharedRocket();
    Rocket *p4 = Rocket::sharedRocket();
    Rocket *p5 = Rocket::sharedRocket();
    
    Rocket* p6 = p1->sharedRocket();
   // delete p6;


    return 0;
}


const成员

const成员:被const修饰的成员变量、非静态成员函数

const成员变量

  • 必须在内部初始化,可以在声明的时候直接初始化赋值
  • 非静态的const的成员变量还可以在初始化列表中初始化

const成员函数(非静态)

  • const关键字写在参数列表后面,函数的声明和实现都必须带const
  • 内部不能修改非static成员变量(重点),
  • 只能调用const成员函数、static成员函数
  • 非const成员函数可以调用const成员函数
  • const成员函数和非const成员函数可以形成重载
  • 非const对象(指针)优先调用非const成员函数
  • const对象只能调用const成员函数,非const可以调用const成员函数
#include <iostream>
using namespace std;
/*
##### const引用

引用可以被const修饰,这样就无法通过引用修改数据了,但可以访问,可以成为常引用

##### const引用特点

可以指向临时数据(常量、表达式、函数返回值等)
*/
class Car {
private:
    int m_x;
    int m_y;
public:
    static int m_age;//静态成员变量 必须要初始化
    static const int m_age1 = 0;//这样可以写
    static const int m_age2 = 1;
    const int m_price = 10;//const成员变量 必须要初始化 这是方式1
    const int m_price1;//必须要初始化
   /* Car() :m_price1(0) {

    }*/
    Car(int peice ) :m_price1(peice) {}//const成员变量 必须要初始化 这方式2


    //const成员函数 声明 
    void display() const;
	//静态成员函数
    static void test() {
        //这里是不可以访问成员变量
        //m_x;//非静态的成员变量不可以访问
        m_age;//静态的可以访问

    }
	//const成员函数
     void test2() const{
      
    }

    void test1() {
        m_x = 209;
        //m_price = 29;
    }

};
//const成员函数 实现
void Car::display() const{//*内部不能修改非static成员变量**
       
    test();//静态成员函数 可以访问
    test2();//const成员函数 可以访问
    //test1();//const成员函数不可以访问非const成员函数
    
    //不可以修改成员变量,可以访问
    cout << "(" << m_x << "," << m_y << ")" << endl;
}

int Car::m_age = 20;//静态成员变量 必须在外边初始化

int main()
{
   
    Car car(0);

    return 0;
}


引用类型的成员
  • 引用类型成员变量必须初始化
  • 在声明的时候初始化
  • 通过初始化列表初始化
拷贝构造

拷贝构造函数是构造函数的一种特殊情况

当利用已存在的对象,创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化;

#include <iostream>
using namespace std;

class Car {
    int m_price;
    int m_length;
public:
    Car(int price = 0, int length = 0) : m_price(price),m_length(length) {
        cout<<"Car(int price = 0, int length = 0)"<<endl;
    }
    //拷贝构造函数
    Car(const Car &car):m_price(car.m_price), m_length(car.m_length){
        cout << "Car(const Car &car)" << endl;
        /*
        拷贝操作,相当于:
        this->m_price = car.m_price;
        this->m_length = car.m_length;
        
        */
        /*
        拷贝操作,相当于:
        m_price = car.m_price;
        m_length = car.m_length;
        */
    }
    void display() {
        cout<<"m_price:"<<m_price<<"\nm_length:"<<m_length<<endl;
    }
};

int main()
{
    Car c(2,3);
    c.display();
    //利用已经存在的c对象创建了一个c4新对象
    //c4初始化时会调用拷贝构造函数
    Car c4(c);
    c4.display();
    return 0;
}

拷贝构造函数2种调用方式

#include <iostream>
using namespace std;

class Car {
    int m_price;
    int m_length;
public:
    Car(int price = 0, int length = 0) : m_price(price),m_length(length) {
        cout<<"Car(int price = 0, int length = 0)"<<endl;
    }
    //拷贝构造函数
    Car(const Car &car):m_price(car.m_price), m_length(car.m_length){
        cout << "Car(const Car &car)" << endl;
    }

    void display() {
        cout<<"m_price:"<<m_price<<"\nm_length:"<<m_length<<endl;
    }
};


int main()
{
    
    Car c1(2,3);
    //拷贝构造方式1
    Car c2(c1);
    //拷贝构造方式2
    Car c3 = c2;
    
    Car c4;//已经存在的对象
    //这里并没有调用拷贝构造函数,仅仅拷贝
    c4 = c3;//已经存在的对象
    c4.display();
    return 0;
}
父类的拷贝构造
#include <iostream>
using namespace std;

class Person {
    int m_age;
public:
    //父类-构造函数
    Person(int age):m_age(age) {
        cout <<"Person(int age):m_age(age)" <<endl;
    }
    //父类-拷贝构造函数
    Person(const Person& person) :m_age(person.m_age) {
        cout << "Person(const Person& person)" << endl;
    }

};

class Student :Person {
    int m_score;
public:
    //构造函数 
    //调用父类的构造函数 Person(age),进行初始化
    Student(int score,int age) : m_score(score),Person(age) {
        cout << "Student(int score,int age)" << endl;
    }
     //拷贝构造函数
    //调用父类的拷贝构造函数 Person(student),进行初始化 ,和调用父类构造函数一个道理
    Student(const Student &student) : m_score(student.m_score), Person(student) {
        cout << " Student(const Student &student)" << endl;
    }

};


int main()
{
    Student st(12,23);
    Student st1(st);


    return 0;
}

浅拷贝

编译器默认的提供的拷贝是浅拷贝

深拷贝
//初始化指针成员变量所遇到的问题
#include <iostream>
using namespace std;

//存在的问题
class Car {
private:
    int m_price;
    char *m_name;
public:
    Car(int price = 0,const char* name = NULL) :m_price(price), m_name(name) {

   }
    void display() {
        cout << "m_price:" << m_price << "\nm_name:" << m_name << endl;
    }
};

Car* g_car2;

void test() {

    //堆空间指向栈空间
    //这个操作很危险
    char name2[] = { 'b','m','w','\0' };
    g_car2 = new Car(101, &name2[0]); //堆空间中的m_name存储的地址值是 局部变量name2的值
    //当调用完test()之后,局部变量被自动回收,m_name变成野指针
}

int main()
{
    //字符串 方式1
    const char* name = "bmw";
    //字符串 方式2
    char name2[] = {'b','m','w','\0'};
    //字符串实际长度 比字符个数 多1
    //cout << strlen(name2) << endl;
    //数组名称 也是个指针,值等于首地址
    Car* car1 = new Car(100, name2);
    Car* car2 = new Car(101, &name2[0]);
    car1->display();
    car2->display();

    
    test();
    g_car2->display();//m_price:101 m_name:烫烫烫烫烫烫烫烫H靨


    return 0;
}

//存在的问题
void test() {

    //堆空间指向栈空间
    //这个操作很危险
    char name2[] = { 'b','m','w','\0' };
    g_car2 = new Car(101, &name2[0]); //堆空间中的m_name存储的地址值是 局部变量name2的值
    //当调用完test()之后,局部变量被自动回收,m_name变成野指针
}
解决堆空间指向栈空间的问题
//解决堆空间指向栈空间的问题
/*
思路:把传给构造函数的 临时数据 保存到堆空间中,让指针指向该空间,这样即使临时变量被销毁,堆空间的也不会消失。
操作:修改m_name的指向,不在指向栈空间name2的地址,而是指向新申请的堆空间的地址
	把传入的参数复制到改堆空间中,使用方法strcpy(dist,src)->从src复制到dist;
*/
#include <iostream>
using namespace std;


class Car {
private:
    int m_price;
    char *m_name;
public:
    /************************解决问题的核心代码*************************/
    Car(int price = 0,const char* name = NULL) :m_price(price){
        if (name!=NULL)
        {
            //申请新的堆空间
            m_name = new char[strlen(name)]{};
            //拷贝字符串数据到新的堆空间
            strcpy(m_name, name);
        }
        
   }
    //拷贝构造
    //(把成员变量的值进行拷贝,并不会考虑指针指向的堆空间,产生野指针),会导致多次堆空间free的问题 
    Car(const Car &car):m_price(car.m_price) {
        
    }
    void display() {
        cout << "m_price:" << m_price << "\nm_name:" << m_name << endl;
    }
    ~Car() {
        //清除堆空间
        if (m_name!=NULL)
        {
            delete[] m_name;
            m_name = NULL;
        }
    }
};
/*^^^^^^^^^^^^^^^^^^^^^^^^解决问题的核心代码^^^^^^^^^^^^^^^^^^^^^^^^*/
Car* g_car2;

void test() {
    char name2[] = { 'b','m','w','\0' };
    g_car2 = new Car(101, &name2[0]);
}

int main()
{
    test();
    g_car2->display();//打印正常
    return 0;
}

如果需要实现深拷贝,就需要自定义拷贝构造函数:

将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间

#include <iostream>
using namespace std;
/*
浅拷贝(shallow copy):指针类型的变量只会拷贝地址值;(把成员变量的值进行拷贝,并不会考虑指针指向的堆空间,产生野指针),会导致多次堆空间free的问题 
https://www.bilibili.com/video/BV1Lo4y1o717?p=87  5分钟
深拷贝(deep copy):将指针指向的内容copy到新的存储空间;

*/

class Car {
private:
    int m_price;
    char *m_name;
    void copy(const char* name = NULL) {
        if (name != NULL)
        {
            //申请新的堆空间
            m_name = new char[strlen(name)+1]{};
            //拷贝字符串数据到新的堆空间
            strcpy(m_name, name);
        }
    }

public:
    //构造函数
    Car(int price = 0,const char* name = NULL) :m_price(price){
        copy(name);
        
    }
    //拷贝构造函数 //深拷贝
    Car(const Car &car):m_price(car.m_price) {
        copy(car.m_name);
    }

    void display() {
        cout << "m_price:" << m_price << "\nm_name:" << m_name << endl;
    }
    ~Car() {
        if (m_name!=NULL)
        {
            delete[] m_name;
            m_name = NULL;
        }
    }

    /*有bug 解决 -》软件问题
    void setName(const char* name = NULL) {
        if (name != NULL)
        {
            delete[] m_name;
            m_name = NULL;
            //cout << name << endl;
            //cout << m_name << endl;
            copy(name);
        }
    }*/
};


int main()
{

    Car car3(100,"BMW");
    Car car4 = car3;

    car4.display();
    //car3.setName("BENCE");
    car3.display();

    return 0;
}

深拷贝
  • 将指针指向的内容copy到新的存储空间;
    • 1、构造的时候把指针指向的内容放到堆空间
    • 2、当产生拷贝构造的时候,第一步产生堆空间里面的内容进行复制到另一个堆空间。
#include <iostream>
using namespace std;
/*
浅拷贝(shallow copy):指针类型的变量只会拷贝地址值;(把成员变量的值进行拷贝,并不会考虑指针指向的堆空间)
深拷贝(deep copy):将指针指向的内容copy到新的存储空间;
			1、构造的时候把指针指向的内容放到堆空间
			2、当产生拷贝构造的时候,第一步产生堆空间里面的内容进行复制到另一个堆空间。
			
*/

class Car {
private:
    int m_price;
    char *m_name;
    
    //复制一个字符到另一个新的堆空间
    void copy(const char* name = NULL) {
        if (name != NULL)
        {
            cout <<"name:" <<name <<",大小:"<< strlen(name) << endl;
            //申请新的堆空间
            m_name = new char[strlen(name)+1]{};
            //拷贝字符串数据到新的堆空间
            strcpy(m_name, name);
        }
    }

public:
    //构造函数
    Car(int price = 0,const char* name = NULL) :m_price(price){
        copy(name);
        
    }
    //拷贝构造函数 //深拷贝
    Car(const Car &car):m_price(car.m_price) {
        copy(car.m_name);
    }

    void display() {
        cout << "m_price:" << m_price << "\nm_name:" << m_name << endl;
    }
    ~Car() {
        if (m_name!=NULL)
        {
            delete[] m_name;
            m_name = NULL;
        }
    }

    void setName(const char* name = NULL) {
        if (name != NULL)
        {
            delete[] m_name;
            m_name = NULL;
            
            //cout << m_name << endl;
            copy(name);
        }
    }
};


int main()
{

    Car car3(100,"BMW");
    Car car4 = car3;

    car4.display();
    car3.setName("BENCE");
    car3.setName("aaaaaa");
    car3.display();
    

    return 0;
}

对象类型的参数和返回值

对象类型的返回值一定会产生额外的对象

注意:返回对象类型的引用最好返回的是调用者本身this所指向的对象,如果返回的是函数内部创建的对象的引用(地址),会出现问题。

#include <iostream>
using namespace std;
//不要返回对象类型:原因是会产生额外的对象
class Car {
public:
    Car() {
        cout << "Car::Car()"<< this << endl;
    }
    Car(const Car &car) {
        cout << "Car(const Car &car)" << this << endl;
    }
    void run() {
        cout << "Car::run()" << endl;
    }
};

void test1(Car car) {//对象作为参数

}
void test2(Car &car) {//引用作为参数

}
void test3(Car* car) {//指针作为参数

}
/*
从函数的栈空间 把car 对象传递给 main函数的栈空间,需要一次拷贝构造
在main函数里面 再次传给新的对象 也需要一次拷贝构造 但编译器优化了

*/
Car test4() {//实际是:2次拷贝构造 1次正常构造 编译器优化了一次拷贝构造
    Car car;//2、自动拷贝构造一个新的对象放到main函数内存里面
    return car;
}

/*
这里返回的是一个 Car类型的对象,并不是原来的car5对象,而是新的对象
*/

//返回值是一个临时对象Car Temp = *car; 
Car test5(Car* car) {    
    return *car;
}
//返回值是个引用 也是个对象,不过没有产生新对象
Car &test6(Car* car) {    
    return *car;
}

int main()
{
    Car car1;
    test1(car1);//产生拷贝构造  1次拷贝构造
    test2(car1);//不会产生
    Car* car2 = &car1;
    test3(car2);
    Car car3;

    //2次拷贝构造 1次正常构造 编译器优化了一次拷贝构造
    car3 = test4();//如何把test4中的car放到main函数里面的?:1、传给test4一个main函数的地址
	
    //这里产生了拷贝构造 ,就是说 有新的对象产生才会调用了拷贝构造函数
    Car car5;
    test5(&car5);//这样会产生 只是没有利用
    Car car6 = test5(&car5);//这里利用了
    
    //这里没有产生新对象,这样才一直使用的是car7对象的地址
    Car car7;
    test6(car7);
    
    return 0;
}

证明:

#include <iostream>
using namespace std;
//不要返回对象类型:原因是会产生额外的对象
class Car {
public:
    Car() {
        cout << "Car::Car()"<< this << endl;
    }
    Car(const Car &car) {
        cout << "Car(const Car &car)" << this << endl;
    }
    void run() {
        cout << "Car::run()" << endl;
    }
    ~Car() {
        cout << "Car::~Car" << this << endl;
    }
};

void test1(Car car) {
    //对象作为参数 
    //Car car = car1; 所以这是拷贝构造
    //Car(const Car &car)0133F868
	//Car::~Car0133F868
}
void test2(Car &car) {//引用作为参数

}
void test3(Car* car) {//指针作为参数

}

Car test4() {//实际是:2次拷贝构造 1次正常构造 编译器优化了一次拷贝构造
    //Car::Car()0069F66B
    Car car;//2、自动拷贝构造一个新的对象放到main函数内存里面 
    return car;
    //Car::~Car0069F66B 这个是test4中的car
}

Car test5(Car* car) {    
    return *car;
}

int main()
{
    Car car1;//Car::Car()0133F98B
    test1(car1);//产生拷贝构造  1次拷贝构造 1次析构 
    
    cout << 1 << endl;
    test2(car1);//不会产生
    Car* car2 = &car1;
    test3(car2);//不会产生
    cout << 2 << endl;

    //Car::Car()0133F973
    Car car3;
    
    //2次拷贝构造 1次正常构造 编译器优化了一次拷贝构造; 
    //Car(const Car &car)0069F6BB  这个是拷贝到main函数中的  car
    car3 = test4();//如何把test4中的car放到main函数里面的?:1、传给test4一个main函数的地址
    //Car::~Car0069F6BB 这个是拷贝到main函数中的 car

    

    return 0;
   
}
//main结束 会调用两次析构
//2次析构
//Car::~Car0069F793 car3
//Car::~Car0069F7AB car1
匿名对象

没有变量名,没有被指针指向的对象,用完后马上调用析构

临时对象不会产生拷贝构造 编译器做了优化

/*匿名对象*/
//临时对象不会产生拷贝构造  编译器做了优化
    cout << 1 << endl; 
    //Car::Car()0069F6AF
    Car car3 = Car();

    //Car::~Car0069F6AF
    cout << 2<< endl;
隐式构造

c++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数

#include <iostream>
using namespace std;

//通过关键字explicit 禁止隐式构造

class Person {
    int m_age;
public:
    Person() {
        cout << "Person::Person()" << this << endl;
    }
    explicit Person(int age):m_age(age) {
        cout << "Person::Person(int age)" << this << endl;
    }
    Person(const Person& person) {
        cout << "Person(const Person &person)" << this << endl;
    }
    void display() {
        cout << "Person::run()"<<this<<m_age<< endl;
    }
    ~Person() {
        cout << "Person::~person" << this << endl;
    }
};

void test1(Person person) {

}

Person test2() {
    return 40;//隐式构造
}

int main()
{
    /*
    Person p1;
    Person p2(10);
    Person p3 = p2;
    */

    //Person p1 = 20; //Person p1(10);//隐式构造

    /*
    test1(20);//隐式构造
    test2();//隐式构造
    Person::Person(int age)00E1F76C
    Person::~person00E1F76C
    Person::Person(int age)00E1F780
    Person::~person00E1F780
    */

    //构造2次
    Person p1;
    p1 = 40;//产生隐式构造 Person(40) 临时对象


    return 0;

}

编译器生成的构造函数
  • 在特定情况下,编译器才会自动生成空的无参构造函数
  • 有定义虚函数
  • 虚继承了其它类
  • 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义的构造函数)
  • 父类有构造函数(编译器生成或自定义的构造函数)

总结:对象创建后,需要做一些额外操作时,比如内存操作,函数调用,编译器一般会为其自动创建构造函数

#include <iostream>
using namespace std;

/*

很多教程都说:编译器会为每一个类都生成空的无参的构造函数

在特定情况下,编译器自动生成空的无参构造函数:比如
* 成员变量在声明的同时初始化
* 

*/
class Person {
public:
    Person() {

    }
};

class Student {
public:
    int m_score = 100;
    /*编译器会在默认构造函数里面 把 m_score = 100;*/
};


int main()
{
    Person person;

    Student student;
    return 0;

}

友元

友元包括友元函数和友元类

如果将函数A(非成员函数)声明为类C的友元函数,那么在函数A内部就能直接访问类C对象的所有成员

如果将类A声明为类C的友元类,那么在类A的所有成员函数内部都能直接访问类C对象的所有成员

#include <iostream>
using namespace std;

class Point {
    //声明友元函数
    friend Point add(Point, Point);
    //声明友元类
    friend class Math;
private:
    int m_x;
    int m_y;
public:
    int getX() { return m_x; };
    int getY() { return m_y; };
    Point(int x, int y) :m_x(x), m_y(y) {}
    void display() {
        cout << "(" << m_x << "," << m_y << ")" << endl;
    }

};
//友元类
class Math {
public:
   
    Point add(Point p1, Point p2) {
        return Point(p1.m_x + p2.m_x, p1.getY() + p2.getY());
    }

    void test() {
        Point p1(2,3);
        p1.m_x = 20;
    }

};

//友元函数 非成员函数
Point add(Point p1, Point p2) {
    return Point(p1.m_x + p2.m_x, p1.getY() + p2.getY());
}
int main()
{
    Point p1(20, 30);
    Point p2(20, 30);
    Point p3 = add(p1, p2);

    p3.display();

    return 0;
}


内部类

如果将类A定义在类C的内部,那么类A就是内部类(嵌套类)

#include <iostream>
using namespace std;

class Person {
    int m_age;
	static int ms_height;
	static void test() {

	}
public:
	//内部类不影响 外部类内存布局,只是内部类访问权限的改变
	class Car {
		int m_age;
		public:
		
			void run() {
				Person person;
				person.m_age = 20;//内部类可以访问外部类所有的成员(反过来不行)

				//内部类可以省略对象名和类名直接访问静态成员
				ms_height = 30;
				test();
			}

	};
};

int main()
{
   
	Person::Car car1;
	Person::Car car2;

	Person p;


    return 0;
}




声明和实现分离

#include <iostream>
using namespace std;
//方式1
//声明
class Point {
    class Math;
};
//实现
class Point::Math{
    test(){
        
    }
}

//方式2
//声明
class Point {
    class Math;
};

class Point::Math{
    test();
}
//实现
class Point::Math::test(){
   
}

//方式3
//声明
class Point {
    class Math{
        void test();
    };
};
//实现
void Point::math::test(){
    
}

局部类
  • 在一个函数内部定义的类
  • 特点
    • 作用于仅限于所在的函数内部
    • 其所有的成员必须定义在类内部,不允许定义static成员变量
    • 成员函数不能直接访问函数的局部变量(static变量除外)
#include <iostream>
using namespace std;

void test() {
	static int age = 10;
	static int ms_age = 10;
	//局部类
	class Car {
		int ms_age;//

		void run() {
			age = 29; //报错

		}
	};
}
int main()
{

	

	return 0;
}


运算符重载

运算符重载:为运算符增加新的功能

#include <iostream>
using namespace std;

class Point {
    //声明友元函数
    friend Point operator+(Point, Point);

private:
    int m_x;
    int m_y;
public:
    int getX() { return m_x; };
    int getY() { return m_y; };
    Point(int x, int y) :m_x(x), m_y(y) {}
    void display() {
        cout << "(" << m_x << "," << m_y << ")" << endl;
    }

};

//友元函数 非成员函数
Point operator+(Point p1, Point p2) {
    cout <<"operator+(Point, Point)" <<endl;
    return Point(p1.m_x + p2.m_x, p1.getY() + p2.getY());
}

int main()
{
    Point p1(20, 30);
    Point p2(20, 30);
    Point p3(20, 30);
    Point p4 = p1 + p2 + p3;//运算符重载 //Point p5 = operator+(p1, p2);
 
    p3.display();
    p4.display();

    return 0;
}


优化

#include <iostream>
using namespace std;
/*
##### const引用

引用可以被const修饰,这样就无法通过引用修改数据了,但可以访问,可以成为常引用

##### const引用特点

可以指向临时数据(常量、表达式、函数返回值等)
*/
class Point {
    //声明友元函数
    friend Point operator+(const Point &, const Point &);

private:
    int m_x;
    int m_y;
public:
    int getX() { return m_x; };
    //const成员函数
    int getY() const { return m_y; };
    Point(int x, int y) :m_x(x), m_y(y) {}
    void display() {
        cout << "(" << m_x << "," << m_y << ")" << endl;
    }

};

//友元函数 非成员函数 const成员变量
Point operator+(const Point &p1,const Point &p2) { //既可以接受常量,又可以接受对象
    cout <<"operator+(Point, Point)" <<endl;
    return Point(p1.m_x + p2.m_x, p1.m_y + p2.getY());
    //内部不能修改非static成员变量(重点),所以只能调用const成员函数、static成员函数
}

int main()
{
    //目的 是为了 p1+p2+p3.....
    const Point p1(20, 30);//常量
    Point p2(20, 30);
    Point p3(20, 30);
    Point p4 = p1 + p2 + p3;//运算符重载 //Point p5 = operator+(p1, p2);
 
    p3.display();
    p4.display();

    return 0;
}

在优化

#include <iostream>
using namespace std;

class Point {
  
private:
    int m_x;
    int m_y;
public:
    Point(int x, int y) :m_x(x), m_y(y) {}
    void display() {
        cout << "(" << m_x << "," << m_y << ")" << endl;
    }

    //既可以接受常量,又可以接受对象
    Point operator+(const Point& point) { 
        cout << "operator+(Point)" << endl;
        return Point(m_x + point.m_x,m_y + point.m_y);
    }

};


int main()
{
    Point p1(20, 30);
    const Point p2(20, 30);
    Point p3(20, 30);
    Point p4 = p1 + p2 + p3;//运算符重载 //Point p5 = operator+(p1, p2);
 
    p3.display();
    p4.display();

    return 0;
}


增加减法

#include <iostream>
using namespace std;

class Point {
  
private:
    int m_x;
    int m_y;
public:
    Point(int x, int y) :m_x(x), m_y(y) {}
    void display() {
        cout << "(" << m_x << "," << m_y << ")" << endl;
    }

    //既可以接受常量,又可以接受对象
    Point operator+(const Point& point) { 
        return Point(m_x + point.m_x,m_y + point.m_y);
    }

    Point operator-(const Point& point) {
        return Point(m_x - point.m_x, m_y - point.m_y);
    }
    Point(const Point &point) {
        cout <<"拷贝构造" <<endl;
    }
     ~Point() {
        cout <<"析构函数" <<endl;
    }
};


int main()
{
    Point p1(20, 30);
    const Point p2(20, 30);
    Point p3(20, 30);
    Point p4 = p1 + p2 + p3;//运算符重载 //Point p5 = operator+(p1, p2);
    p4 = p4 - p3;//产生拷贝构造吗?不产生,因为没有
    p3.display();
    p4.display();

    return 0;
}


临时对象不会产生拷贝构造

原因:编译器做了优化

#include <iostream>
using namespace std;

class Point {
  
private:
    int m_x;
    int m_y;
    char m_z;
public:
    Point(int x, int y,char z) :m_x(x), m_y(y),m_z(z) {
        cout << "构造函数" << m_z << this << endl;
    }
    void display() {
        cout << "(" << m_x << "," << m_y << ")" << endl;
    }

    //既可以接受常量,又可以接受对象
    Point operator+(const Point& point) { 
        return Point(m_x + point.m_x,m_y + point.m_y, m_z);
    }

    Point operator-(const Point& point) {
        return Point(m_x - point.m_x, m_y - point.m_y, m_z);
    }
    Point(const Point &point) {
        cout <<"拷贝构造" << this <<endl;
    }
    ~Point() {
        cout << "析构函数" << this <<endl;
    }
};


int main()
{
    Point p1(20, 30,'a');//构造函数a001FF96C
    cout << "1" << endl;
    Point p2(20, 30,'b');// 构造函数b001FF958
    cout << "1" << endl;
    Point p3(20, 30,'c');//构造函数c001FF944
    cout << "1" << endl;
    
    Point p4 = p1 + p2 + p3;//运算符重载 //Point p5 = operator+(p1, p2);
    //p4 = p4 - p3;//产生拷贝构造吗?不产生
    cout << "1" << endl;
    cout << &p4 << endl;
    p3.display();
    p4.display();

    Point p5 = Point(23,23,'d');//临时对象不会产生拷贝构造

    /*
    构造函数a001FF96C
    1
    构造函数b001FF958
    1
    构造函数c001FF944
    1
    构造函数a001FF85C p1 + p2 = &
    构造函数a001FF930 & + p3 = p4

    析构函数001FF85C
    1
    001FF930 p4
    (20,30)
    (60,90)
    析构函数001FF930 p4
    析构函数001FF944 p3
    析构函数001FF958 p2
    析构函数001FF96C p1
    
    */
    return 0;
}



模板

泛型,是一种将 [类型参数化] 以达到代码复用的技术,C++中使用模板来实现泛型

模板使用格式如下

template <typename T> T add(T a,T b){
    return a+b;
}
//T 将来可以发生变化
#include <iostream>
using namespace std;

class Point {
    friend ostream& operator<<(ostream&, const Point&);
    int m_x;
    int m_y;
public:
    Point(int x,int y):m_x(x),m_y(y){}
    const Point operator+(const Point &point) {
        return Point(m_x+point.m_x,m_y+point.m_y);
    }
    
};
ostream &operator<<(ostream &cout, const Point& point) {
    cout << "("<<point.m_x << ","<<point.m_y<<")";
    return cout;
}

//int add(int a, int b) {
//    return a + b;
//}
//Point add(Point a, Point b) {
//    return a + b;
//}

template <typename T> T add(T a,T b) {
    return a + b;
}

/*
这种写法也可以
template <class T> T add(T a,T b) {
    return a + b;
}

*/

/*
相当把类型当做参数
template <typename T = 数据类型> T add(T a,T b) {
    return a + b;
}
*/

int main()
{
    add<int>(10, 20);
    cout << add(10, 20) << endl;
    cout << add<Point>(Point(10, 20), Point(10, 20)) << endl;


    cout << add(10, 20) << endl;
    cout << add(Point(10, 20), Point(10, 20)) << endl;
    return 0;
}

模板没有被使用时,是不会生成函数实现;

C++编译过程
  1. 头文件不参与编译,cpp文件单独编译
  2. main文件编译的时候,会把头文件代码直接拿过来
  3. 函数调用会编译成call 临时函数地址–不是真的地址
  4. 编译完成后开始链接,链接的时候,把矫正临时的地址,把真正的地址放进去
  5. 最后生成exe可执行文件
模板的声明

模板的声明和实现如果分离到.h和.cpp中(cpp文件是单独编译的,模板单独编译的情况下,不会生成函数实现),会导致连接错误(寻找真正的函数地址的时候找不到),所以要都放在头文件里面(.hpp)

//头文件
template <typename T> T add(T a,T b) {
    return a + b;
}
//main文件
#include <iostream>
#include "add.h"
using namespace std;


class Point {
    friend ostream& operator<<(ostream&, const Point&);
    int m_x;
    int m_y;
public:
    Point(int x,int y):m_x(x),m_y(y){}
    const Point operator+(const Point &point) {
        return Point(m_x+point.m_x,m_y+point.m_y);
    }
    
};
ostream &operator<<(ostream &cout, const Point& point) {
    cout << "("<<point.m_x << ","<<point.m_y<<")";
    return cout;
}
int main()
{
    add<int>(10, 20);
    cout << add(10, 20) << endl;
    cout << add<Point>(Point(10, 20), Point(10, 20)) << endl;


    cout << add(10, 20) << endl;
    cout << add(Point(10, 20), Point(10, 20)) << endl;
    return 0;
}

类模板

没有使用类模板之前:

#include <iostream>
using namespace std;



class Array {
    int *m_data;
    int m_size = 0;
    int m_capacity;
public:
    Array(int capacity) {
        m_capacity = (capacity > 0) ? capacity : 10;
        m_data = new int[m_capacity];
    }
    
    ~Array() {
        if (m_data == NULL) return;
        delete[] m_data;
    }

    int getSize() {
        return m_size;
    }
    void add(int value) {
        if (m_size == m_capacity) {
            m_capacity = m_capacity*2;
            //扩容
            int* temp_data = new int[m_capacity];
            for (int i = 0; i < m_size;i++) {
                temp_data[i] = m_data[i];
            }
            delete[] m_data;
            m_data = temp_data;
           
        }
        m_data[m_size++] = value;
    }
    //获取当前索引
    int get(int index) {
        if (index<0 || index >= m_size) return 0;//抛出异常
        return m_data[index];
    }

    int operator[](int index) {
        return get(index);
    }


};

int main()
{
    Array data(5);
    
    data.add(10);
    data.add(2);
    data.add(1);
    data.add(3);
    data.add(4);
    data.add(15);
   

    cout << data.getSize() << endl;
    cout << data[2]<< endl;
    cout << data[10]<< endl;

    return 0;
}


使用之后:

#include <iostream>
using namespace std;


template <class Item>
class Array {
    Item*m_data;
    int m_size;
    int m_capacity;
public:
    //初始化数组 申请数组空间大小
    Array(int capacity) :m_size(0){
        m_capacity = (capacity > 0) ? capacity : 10;
        m_data = new Item[m_capacity];
    }
    
    ~Array() {
        if (m_data == NULL) return;
        delete[] m_data;
    }

    int getSize() {
        return m_size;
    }
    void add(Item value) {
        if (m_size == m_capacity) {
            m_capacity = m_capacity*2;
            //扩容
            Item* temp_data = new Item[m_capacity];
            for (int i = 0; i < m_size;i++) {
                temp_data[i] = m_data[i];
            }
            delete[] m_data;
            m_data = temp_data;
           
        }
        m_data[m_size++] = value;
    }
    //获取当前索引
    Item get(int index) {
        if (index<0 || index >= m_size) return 0;//抛出异常
        return m_data[index];
    }
	
    Item operator[](int index) {
        return get(index);
    }


};

int main()
{
    Array<int> data(5);
    
    data.add(10);
    data.add(2);
    data.add(1);
    data.add(3);
    data.add(4);
    data.add(15);
   

    cout << data.getSize() << endl;
    cout << data[2]<< endl;
    cout << data[10]<< endl;

    return 0;
}


万能指针 void *

#include <iostream>
#include "../40-友元/40-友元.cpp"
using namespace std;


template <class Item>
class Array {
    Item*m_data;
    int m_size;
    int m_capacity;
public:
    //初始化数组 申请数组空间大小
    Array(int capacity = 0) :m_size(0){
        m_capacity = (capacity > 0) ? capacity : 10;
        m_data = new Item[m_capacity];
    }
    
    ~Array() {
        if (m_data == NULL) return;
        delete[] m_data;
    }

    int getSize() {
        return m_size;
    }
    void add(Item value) {
        if (m_size == m_capacity) {
            m_capacity = m_capacity*2;
            //扩容
            Item* temp_data = new Item[m_capacity];
            for (int i = 0; i < m_size;i++) {
                temp_data[i] = m_data[i];
            }
            delete[] m_data;
            m_data = temp_data;
           
        }
        m_data[m_size++] = value;
    }
    //获取当前索引
    Item get(int index) {
        if (index<0 || index >= m_size) return 0;//抛出异常
        return m_data[index];
    }

    Item operator[](int index) {
        return get(index);
    }


};

int main()
{
    Array<int> data(5);
    
    Array<void*>array;//void * 万能指针 万能数组
    array.add(&Point(2,3));
    array.add(new Point(2,3));

    data.add(10);
    data.add(2);
    data.add(1);
    data.add(3);
    data.add(4);
    data.add(15);
   

    cout << data.getSize() << endl;
    cout << data[2]<< endl;
    cout << data[10]<< endl;

    return 0;
}


类模板声明
#pragma once
#include <iostream>
using namespace std;

template <class Item>
class Array {
    friend ostream& operator<< <>(ostream& , const Array<Item>& );
    Item* m_data;
    int m_size;
    int m_capacity;
public:
    //初始化数组 申请数组空间大小
    Array(int capacity = 0);//默认参数只能写在声明中
    ~Array();
    int getSize();
    void add(Item value);
    void remove(int index);

    Item get(int index)const;
    //重载[]
    Item operator[](int index);// 访问array[0]
  
};

template <class Item>
Array<Item>::Array(int capacity) :m_size(0) {
    m_capacity = (capacity > 0) ? capacity : 10;
    m_data = new Item[m_capacity];//默认的数组创建方式
}

template <class Item>
Array<Item>::~Array() {
    if (m_data == NULL) return;
    delete[] m_data;
}

template <class Item>
int Array<Item>::getSize() {
    return m_size;
}

template <class Item>
void Array<Item> ::add(Item value) {
    if (m_size == m_capacity) {
        m_capacity = m_capacity * 2;
        //扩容
        Item* temp_data = new Item[m_capacity];
        for (int i = 0; i < m_size; i++) {
            temp_data[i] = m_data[i];
        }
        delete[] m_data;
        m_data = temp_data;

    }
    m_data[m_size++] = value;
}
template <class Item>
void Array<Item> ::remove(int index) {
    if (index < 0 || index >= m_size) return;//越界异常--抛出异常
    if (index != m_size - 1) {
        for (int i = index; i < m_size; i++) {
            m_data[i] = m_data[i + 1];
        }
    }
    m_size--;

}

template <class Item>
Item Array<Item>::get(int index) const{
    if (index < 0 || index >= m_size) return;//抛出越界异常
    return m_data[index];
}

template <class Item>
Item Array<Item> ::operator[](int index) {
    return get(index);
}

template <class Item>
ostream & operator<< (ostream & cout, const Array<Item> &array) {
    cout << "[";    
    for (int i = 0; i < array.m_size;i++) {
        if (i != 0) cout << ",";
        cout << array.m_data[i];
    }
    return  cout << "]";
}
//main.cpp
#include <iostream>
#include "Array.hpp"

using namespace std;
class Point {
private:
    friend ostream& operator<< (ostream&, const Point&);
    int m_x;
    int m_y;
    
public:
    Point(int x = 0, int y = 0) :m_x(x), m_y(y) {};   
};

ostream & operator<< (ostream& cout, const Point& point) {
    cout << "(" << point.m_x << "," << point.m_y << ")";
    return cout;
}


int main()
{
        
    Array<Point> array;//void * 万能指针 万能数组
    array.add(Point(1,2));
    array.add(Point(2,2));
    array.add(Point(3,2));
    array.add(Point(4,2));
    array.remove(2);
    array.remove(7);

    cout << array << endl;

    return 0;
}


实现插入功能

/*
Array.hpp
...
*/
    void insert(int index,const Item &array);
/*
...
*/

template <class Item>
void Array<Item> ::insert(int index, const Item& array) {
    if (index < 0 || index >= m_size) return;//越界异常--抛出异常
    m_size++;
    if (m_size == m_capacity) {
        m_capacity = m_capacity * 2;
        //扩容
        Item* temp_data = new Item[m_capacity];
        for (int i = 0; i < m_size; i++) {
            temp_data[i] = m_data[i];
        }
        delete[] m_data;
        m_data = temp_data;

    }
    //如果不是在最后一个地方插入
    if (index != m_size - 2) {
        for (int i = m_size - 2; i > index; i--) {
            m_data[i + 1] = m_data[i];
        }
    }
    m_data[index] = array;
    
}

/*
...
*/
/*
main.cpp
...
*/
    
array.insert(1,Point(22,454));
/*
...
*/
类型转换

类型转换的本质都是赋值,只是为了骗过编译器可以编译通过,本质都是赋值

//c语言风格的类型转换符
//类型 表达式 
(type )expression
//
type(expression)
//隐式转换
int a = 10 ;
double b = a;

//c++中有四个类型转换符
static_cast;//
dynamic_cast;//
reinterpret_cast;//
const_cast;//去除const属性

xxx_cast<type>(expression);
int a = 10;
const_cast

用于去除const属性

const_cast<double>(a);

const Person* p1 = new Person();
//C++语言风格
Person *p2 = const_cast<Person *>(p1);
//C语言风格
Person *p2 = (Person *)p1;
dynamic_cast

用于多态类型的转换

class Person{};
class Student:Person{};
class Car{};

Person *p1 = new Person();
Person *p1 = new Student();

Car *car = dynamic_cast<Car *>(p1);//可以交叉转换

//C++会做安全检测、C 语言不会做安全检测--纯粹的赋值操作
Student *stu1 = dynamic_cast<Student *>(p1);
//相当于 Student *stu1 = new Person(); 子类指针指向父类对象 不安全  
//Student *stu1 = NULL;直接变成空指针

Student *stu2 = dynamic_cast<Student *>(p2);
//相当于 Student *stu1 = new Student(); 安全
static_cast

对比dynamic_cast,缺乏运行时安全检测

不能交叉转换(不是同一继承体系的,无法转换),没有任何联系的进行转换

常用于基本数据类型的转换、非const转成const,和const_cast反过来

class Person{};
class Student:Person{};
class Car{};

Person *p1 = new Person();

const Person *p2 = p1;//隐式转换 非const转成const
const Person *p2 = static_cast<Person *>(p1);//非const转成const

Person *p1 = new Student();

int a = 10;
double b =  static_cast<double>(a);

//不会做安全检测
Student *stu1 = static_cast<Student *>(p1);
Student *stu2 = static_cast<Student *>(p2);

Car *car = dynamic_cast<Car *>(p1);//能交叉转换
Car *car = static_cast<Car *>(p1);//不能交叉转换



reinterpret_cast

属于比较底层的强制转换、没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝

class Person{};
class Student:Person{};
class Car{};
//小端模式
//0000 1010  0000 0000  0000 0000  0000 0000 二进制 4个字节
//0A 00 00 00 十六进制 4个字节
int a = 10;
//0A 00 24 40
double b= a;
//0A 00 00 00 CC CC CC CC
double b= reinterpret_cast<double>(a);

cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;

int *d= reinterpret_cast<int *>(0x1100);
int *f= reinterpret_cast<int *>(d);
cout<<"d="<<d<<endl;
cout<<"f="<<f<<endl;
C++11标准
  • 新特性
    • auto:自动推断出数据类型,属于编译器特性,不会影响运行效率
    • decltype:可以获取变量类型 decltype(a)
    • nullptr: 空指针 ,解决NULL二义性的问题
    • 快速遍历数组
    • Lambda表达式:类似于JavaScript里面的闭包,本质就是函数
int a = 19; 
decltype(a) b = 10;//获取a 的类型
//nullptr: 空指针 ,解决NULL二义性的问题
void func(int v){}
void func(int *v){}

func(NUll);//调用 int v  C++98会产生二义性
int *p = (int *) 10;
func(NUll);//调用 int *v
func(nullptr);//调用 int *v

NUll是整数  0
nullptr是空指针  00000000

/*清空指针最新的方式*/
int *p = new int ;
delete p;
p = nullptr;

int array[] = {1,2,3,4,5,6,7};
for(int item : array){
    cout<<item<<endl;
}

int array[]{1,2,3,4,5,6,7};

/*
完整结构:
[capture list](params list) mutable exception -> return type{function body}
capture list :捕获外部变量列表
params list :形参列表,不能使用默认参数。不能使用省略参数名
multabe:用来说明是否可以修改捕获的变量
exception:异常设定
return type:返回值类型
function body:函数体
有时可以省略部分结构:
[capture list](params list) -> return type{function body}
[capture list](params list){function body}
[capture list]{function body}
*/

void func(){
    cout<<"func()"<<endl;
}

int main(){
    //lambda表达式 
    ([]{
    	cout<<"func()"<<endl;
	})();
    
    //存储lambda表达式
    void (*p)() = []{
    	cout<<"func()"<<endl;
	}
    //
    int (*p)(int,int) = [](int a,int b)->int{
    	cout<<"func()"<<endl;
	}
    cout<<p1(10,20)<<endl;
    不带参数 和返回类型
    auto p = []{
    	cout<<"func()"<<endl;
	}
    //带参数 和返回类型
    auto p = [](int a,int b)->int{
        return a+b;
    }
    
}

#include <iostream>
using namespace std;

int exec(int v1,int v2,int (* func)(int,int)) {
    return func(v1,v2);
}


int main()
{
   
    cout << exec(2, 3, [](int v1, int v2) {return v1 + v2; }) << endl;
    cout << exec(2, 3, [](int v1, int v2) {return v1 - v2; }) << endl;
    cout << exec(2, 3, [](int v1, int v2) {return v1 * v2; }) << endl;
    cout << exec(2, 3, [](int v1, int v2) {return v1 / v2; }) << endl;

    return;
}

//捕获
#include <iostream>
using namespace std;

int exec(int v1,int v2,int (* func)(int,int)) {
    return func(v1,v2);
}


int main()
{
   //全局变量不分 值捕获和地址捕获
    int a = 10;
    int b = 10;
    //默认都是值捕获 不可以操作参数
    auto func1 = [a, b] {
        cout << a << endl;
    };

    //地址捕获 可以操作
    auto func2 = [&a,& b] {
        a++;
        b++;
        cout << a << endl;
    };
    //地址捕获
    auto func3 = [&] {
        cout << a << endl;
    };
    //a是隐式捕获,其余都是地址捕获
    auto func4 = [&,a] {
        cout << a << endl;
    };

    //默认都是值捕获
    /*auto func1 = [a] {
        a++; 报错
    };*/

    auto func5 = [&a] {
        a++;
    };

    /*可以修改,但是不是修改的外边的*/
    auto func6 = [a]() mutable {
        /*
        int a; 相当于这里重新定义了一个a
        */
        a++;
        cout << a << endl;
    };

    
    auto func1 = [auto a,auto b] {
        cout << a << endl;
    };
    //a = 20;

    /*func1();
    func2();*/

    func6();
    cout << a << endl;

    return 0;
}

//泛型lambda表达式 C++14版本
auto func1 = [](auto v1, auto v2) {return v1 + v2;};
cout << func1(10,10.23) << endl;
//仅仅对捕获变量进行初始化
int a;
auto func2 = [a = 10](){
    cout<<a<<endl;
}
func();
//这里仍然是未初始化;
 cout<<a<<endl;

//作用域 C++17版本
int main()
{
    //变量a,b作用域是它所在的if语句。以及其后边的if-else语句
    if (int a = 10 ; a<=10) {
        a = 1;
    }
    else if (int b = 20; a > 10) {
        b = 2;
        a = 2;
    }
  
    return 0;
}
异常

语法错误

逻辑错误

异常:程序运行中可能会发生的错误(内存不够)

异常没有人处理可能会终止程序

捕捉
#include <iostream>
using namespace std;

int main()
{
    cout << 1 << endl;

    for (int i = 0; i < 10000; i++) {
        //这句代码可能会产生异常
        try {
            int* p = new int[99999];
        }
        catch (...) { //... 捕获所有类型异常
            cout << "产生异常了:内存不够" << endl;
            break;
        }
    }
    cout << 2 << endl;

    return 0;
}

//无法捕获异常的情况
int main()
{
    cout << 1 << endl;
    int a = 0;
    int b = 10;
    try
    {
        int c = divide(b,a);
    }
    catch (...)
    {
        cout << "产生异常了" << endl;
    }
    cout << 2 << endl;


    return 0;
}
抛出
//主动抛出异常
int divide(int v1,int v2) {
    if (v2 == 0) {
        //抛出异常 
        throw 999;
    }
    return v1 / v2; //系统无法捕获除法异常
}

int main()
{
    cout << 1 << endl;
    int a = 0;
    int b = 10;
    try
    {
        int c = divide(b,a);
    }
    catch (int exception)//int exception
    {
        cout << "产生异常了" << exception << endl;
    }
    cout << 2 << endl;


    return 0;
}

//catch可以写多个
int divide(int v1,int v2) {
    if (v2 == 0) {
        //抛出异常 
        throw "不能除以0";
        
    }
    return v1 / v2; //系统无法捕获除法异常
}


int main()
{
  
    cout << 1 << endl;
    int a = 0;
    int b = 10;
    try
    {
        int c = divide(b,a);
    }//catch可以写多个,类似于重载
    catch (const char *exception)
    {
        cout << "产生异常了,const char *" << exception << endl;
    }
    catch (int exception)
    {
        cout << "产生异常了,int " << exception << endl;
    } 


    cout << 2 << endl;


    return 0;
}

throw异常的规则

//throw异常后,会在当前函数中查找匹配的catch,找不到就终止当前函数代码,去一层函数去查找,如果最终都查不到匹配的catch,就终止整个程序
void func1() {
    cout<<"func1-begin"<<endl;
    throw 999;
    cout << "func1-end" << endl;
}

void func2() {
    cout << "func1-begin" << endl;
    func1();
    cout << "func1-end" << endl;
}

int main()
{
    func2();
    return 0;
}

//在当前函数解决异常
void func1() {
    cout << "func1-begin" << endl;
    try
    {
        throw 999;
    }
    catch (int exception)
    {
        cout << "产生异常了" << endl;
    }

    cout << "func1-end" << endl;
}

void func2() {
    cout << "func1-begin" << endl;
    func1();
    cout << "func1-end" << endl;
}

int main()
{
    func2();


    return 0;
}

//在上一级函数解决异常
void func2() {
    cout << "func1-begin" << endl;
    try
    {
        func1();
    }
    catch (int exception)
    {
        cout << "产生异常了" << endl;
    }

    cout << "func1-end" << endl;
}

//在最后的函数解决异常

int main()
{
   
    try
    {
        func2();
    }
    catch (int exception)
    {
        cout << "产生异常了" << endl;
    }
    return 0;
}


抛出异常声明
int divide(int ,int) throw(int);//抛出int类型异常
void func2();//抛出任何异常
void func1() throw();//不抛出任何异常
void func3() throw(int,double);//只抛出int double类型异常
自定义异常
//这种自定义的没有意义
int divide(int v1,int v2) {
    if (v2 == 0) {
        //抛出异常 
        throw "不能除以0";
    }
    return v1 / v2; //系统无法捕获除法异常
}

int main()
{
    cout << 1 << endl;
    int a = 0;
    int b = 10;
    try
    {
        int c = divide(b,a);
    }
    catch (const char *exception)
    {
        cout << "产生异常了," << exception << endl;
    }
    cout << 2 << endl;


    return 0;
}

有意义的异常

#include <iostream>
using namespace std;

//int divide(int, int) throw (Exception);

class Exception {
public:
    //接收子类异常类型 和 异常code
    Exception(const char* temp,int code) {
        cout<<"抛出" << temp <<"异常,异常code"<<code<<":";
    }
    virtual void solve() const = 0;
};

class DivideException :public Exception {
 public:
     void solve() const {
        cout<< "如何解决?请百度!";
    }
    //只要生成 子类构造器 就会调用父类构造器
    //调用父类构造器 传入子类异常类型  异常code
    DivideException():Exception("DivideException",201) {
        cout << "不能除以0," ;
    }


};
class AddException :public Exception {
public:
    AddException() :Exception("AddException",202) {
        cout << "加法还能有异常???";
    }
};
int divide(int v1, int v2) {
    if (v2 == 0) {
        throw DivideException();
    }
    return v1 / v2; //系统无法捕获除法异常
}

int main()
{
    
    try
    {
        divide(0,0);
    }
    catch (const Exception &exception)
    {
        exception.solve();
    }


    return 0;
}

智能指针
非智能指针-传统指针

需要手动管理内存

容易发生内存泄漏(忘记释放,出现异常)

释放之后产生野指针

//内存泄漏
void test(){
    
    throw 999;
}

int main(){
    try{
        int *p  = new int();
        test();//出现异常
        //这句代码无效
    	delete p;
    }catch(...){
        
    }
   int *q = new int();
    delete q;
    q->//野指针
    
    return 0;
}
智能指针

为了解决传统指针存在的问题

auto_ptr:属于C++98标准,在C++11中已经不推荐使用,有缺陷,比如不能使用数组

shared_pty:属于C++11标准

unique_pty:属于C++11标准

auto_ptr
auto_ptr<Person> p(new Person[]);//报错 不支持数组
#include <iostream>
using namespace std;

class Person {
    int m_age;
public:
       Person(){
           cout<<"Person()"<<endl;
       }
       Person(int age):m_age(age) {
           cout << "Person()" << endl;
       }
       ~Person(){
           cout << "~Person()" << endl;
       }
       void run() {
           cout << " run()" << endl;
       }
};



int main() {
    //智能指针对象
    auto_ptr<Person> p(new Person(10));
    //不支持隐式构造
    //auto_ptr<Person> p = new Person();

    p->run();
    {
         //报错的原因是:double free
   		 Person person(29);       
        //直接报错
        auto_ptr<Person> p1(person);
        //原因 是指针指向的栈空间的对象,               
    }//在这里执行两次析构函数:Person person(29); 和 p1
    //当调用p1析构函数的时候,发现指向person的指针已经销毁,就会报错;

    return 0;
}

自实现智能指针
#include <iostream>
using namespace std;

class Person {
    int m_age;
public:
       Person(){
           cout<<"Person()"<<endl;
       }
       Person(int age):m_age(age) {
           cout << "Person()" << endl;
       }
       ~Person(){
           cout << "~Person()" << endl;
       }
       void run() {
           cout << " run()" << endl;
       }
};
template <typename T>
class SmartPointer {
private:
    T* m_obj;
public:
    SmartPointer(T *obj):m_obj(obj) {}
    ~SmartPointer() {
        if (m_obj == nullptr) return;
        delete m_obj;
        m_obj = nullptr;
    }
    T *operator->() {
        return m_obj;
    }

};


int main() {

    SmartPointer<Person> p(new Person(20));

    p->run();

    return 0;
}
shared_pty

:对一个对象产生强引用(strong reference)

每一个对象都有与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着

当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1

当有一个shared_pty销毁时(比如作用域结束),对象的强引用计数就会-1

当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)

cout << "1" << endl;
    {
        shared_ptr<Person> p5;
        {
            
            //shared_ptr<Person[]> p(new Person[5]());
            shared_ptr<Person> p1(new Person(10));
            cout<<p1.use_count()<<endl;
            shared_ptr<Person> p2 = p1;
            cout << p1.use_count() << endl;
            shared_ptr<Person> p3 = p1;
            cout << p1.use_count() << endl;
            shared_ptr<Person> p4 = p1;
            cout << p1.use_count() << endl;
            p5 = p4;//只有当最后一个智能指针销毁的时候,new Person才会销毁
            cout << p1.use_count() << endl;
        }
        cout << p5.use_count() << endl;
        cout << "2" << endl;
    }

    cout << "3" << endl;
double free
 //double free
Person* p = new Person();
{
    shared_ptr<Person> p1(p);
}//~Person()
{
    shared_ptr<Person> p2(p);
}//~Person()
循环引用
#include <iostream>
using namespace std;

class Person;

class Car {
public:
    shared_ptr<Person> m_person = nullptr;
    Car() {
        cout << "Car()" << endl;
    }
    ~Car() {
        cout << "~Car()" << endl;
    }

};

class Person {   
public:
    shared_ptr<Car> m_car = nullptr;
    Person() {
        cout << "Person()" << endl;
    }
    ~Person() {
        cout << "~Person()" << endl;
    }
};

int main() {
    
    {
        shared_ptr<Person> person(new Person);
        shared_ptr<Car> car(new Car);
        person->m_car = car;//
        car->m_person = person;//
    }
	//new Person 和new Car 不会被释放,原因就是强引用导致的 循环引用
    //强引用计数器不为零,就不会调用析构   
    
    return 0;
}
使用弱引用解决循环引用
#include <iostream>
using namespace std;

class Person;

class Car {
public:
    weak_ptr<Person> m_person;
    Car() {
        cout << "Car()" << endl;
    }
    ~Car() {
        cout << "~Car()" << endl;
    }

};

class Person {   
public:
    shared_ptr<Car> m_car = nullptr;
    Person() {
        cout << "Person()" << endl;
    }
    ~Person() {
        cout << "~Person()" << endl;
    }
};

int main() {
    
    {
        shared_ptr<Person> person(new Person);
        shared_ptr<Car> car(new Car);
        person->m_car = car;
        car->m_person = person;
    }

    return 0;
}
unique_pty

同一时间只能指向一个对象

unique_ptr<Person> p0;
{
	unique_ptr<Person> p1();
	p0 = std::move(p1);//可以转移指向
}

汇编

x64汇编-寄存器

64bit:以R开头

通用寄存器:RAX\RBX\RCX\RDX

一个寄存器存8个字节

32bit:以E开头

通用寄存器:EAX\EBX\ECX\EDX

16bit

通用寄存器:AX\BX\CX\DX

汇编指令
mov dest,src
//将src的内容赋值给dest,类似于dest = src

[地址值] h是十六进制
//中括号[]里面放的都是内存地址

int a =3; 

//将3放到[ebp-8]内存地址所对应的空间
mov word ptr [ebp-8],3
//对应的汇编指令 word/2个字节 ptr/指定单位大小 

mov eax,dword ptr [ebp-8] //从这个内存地址开始取 4个字节 放到eax中 
// dword/4个字节 double word
//qword/8个字节 quad word
call [内存地址] //调用函数    
jmp  [内存地址] //跳转地址
    
lea eax,[1122h] //把地址值 给 eax 装载地址值
//相当于 eax == 1122h

ret  //返回 
rep //重复执行 这段代码    
xor op1,op2//将op1和op2异或的结果赋值给op1,类似于op1 = op1^op2
add op1,op2//op1 = op1+op2
sub op1,op2//op1 = op1-op2
inc op1//op1 = op1+1  自加
dec op1//op1 = op1-1  自减
cmp  eax,op2//比较 eax和op2的值 是否相等
jne //jump not equal 不相等的时候才跳转

[ebp-8]// ebp 栈空间地址
    

每个内存地址对应一个内存字节

一个变量的地址值,是它所有字节地址中的最小值

内存大小表示:一个字节 = 两个16进制 = 8个二进制 : 8 = 08 = 00000100

大小端:小段:高高低低,高字节放高地址,低字节放低地址

内联汇编
int a = 10;
__asm{
    //汇编代码
    mov eax,a
    mov ax,10
    mov eax,11223344h
}

反汇编
//两个十六进制 是一个字节 
001F4780 8B F4                mov         esi,esp  
001F4782 68 3C 10 1F 00       push        1F103Ch 

 (地址1)001F4780 8B F4 (2个字节)              mov         esi,esp  
 (地址2)001F4782 68 3C 10 1F 00(5个字节)       push        1F103Ch 
地址1和地址2正好相差2个字节

013447BA 6A 0A                push        0Ah  
013447BC E8 22 CC FF FF       call        display (013413E3h)//函数地址 

display://在这里
013413E3 E9 28 06 00 00       jmp//跳转这个地址  01341A10 

01341A10 55                   push        ebp  //转到了这里
01341A11 8B EC                mov         ebp,esp


逆向破解工具
名称地址
IDA Prohttp://www.idapro.com.cn/
LordPE
PEID
吾爱破解OD
PETool
WinHex
汇编金手指
ResourceHacker
OD
AsmFun
Cheat Engine
CodeinEX
Dbgview
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

语音不识别

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值