自下而上的学习面向对象的C++笔记

语言学习书籍:primer C++ 中文 第五版

链接说明:语言学习初学者的困惑与其对应的解决方案

1.C++基础

1.1 初学疑问

  • 想进游戏公司为什么学习C++语言?

一般开发游戏使用的语言:

2D页游:AS3 JS
3D页游:AS3 C#(Unity)
IOS游戏:Obj-C js/lua(Cocos-2d-x) js/C#(Unity) AS3
安卓游戏:java js/lua(Cocos-2d-x) js/C#(Unity) AS3
在线小游戏:AS3 JS
大型单机游戏/客户端MMORPG::C++ C#

为什么育碧之类的大公司依然坚定的用C++?

1 工业化好:上中下游都直接首先支持C++,民间普及度也高
2 可以手动控制各种细节,优化的空间非常大。同样的游戏,肯定优先选更流畅逼真的。
3 可以更好的团队开发。

如上是笔者个人提出的问题,但相比而言有自己的作品和扎实的基础才是面试游戏公司的底气。

  • C为什么能直接对硬件进行操作?
这个问题是错误的,只有机器语言才能对硬件直接进行操作。
C语言接近硬件是指它可以直接用指针访问硬件地址。
所以,相对来说,C语言在高级语言当中是最贴近物理层面的语言。
  • 如何用C语言实现面向对象

C语言实现面向对象学习链接 1
C语言实现面向对象学习链接 2

1.2 C到C++的过渡

  • 为什么要引入命名空间的概念

解释命名空间的链接
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。
在C++中,变量、函数和类都是大量存在的。
如果没有命名空间,这些变量、函数、类的名称将都存在于全局命名空间中,会导致很多冲突。

目前初学,记住这句话
允许命名空间名字相同,但在此基础上不能允许出现同名变量,否则会报错重复定义。

举例说明:在main.cpp使用其他文件的命名空间

//func1.cpp
#include <iostream>
namespace A
{
    int a = 5;
}

void func1()
{
    std::cout << A::a << std::endl;
}
//func2.cpp
#include <iostream>

namespace B
{
    int a = 5;
}
using namespace std;

void func2()
{
    cout << B::a << endl;
}
//main.cpp
#include <iostream>
//有了如下两行声明才可使用
extern void func1();
extern void func2();

using namespace std;

int main()
{
    func1();
    func2();
}

源文件不能访问其他源文件的命名空间

扫描二维码关注公众号,回复: 11624870 查看本文章
  • 为了避免头文件重定义
#pragma once

//等同于

#ifndef __FUNC1_H
#define __FUNC1_H

#endif
#include <iostream>
using namespace std;
int main(void)
{
    int a,b;
    cin >> a >> b;
    cout << "a=" << a << ",b=" << b << endl;
    char str[100];
    char ch = cin.get();
    printf("ch=%d",ch);
    cout << "ch= " << ch << endl;
    cin.getline(str,100);
    return 0;
}
  • for循环的升级
    简述一下就是将变量的声明与初始化限制在for循环内部。
    虽然C标准的升级后也允许这样,但C++一开始就如此设定。
for(int i=0;i<2;i++)
{
	.....
}
  • 关键字的升级


register:尽可能将变量保存到cpu内部寄存器中,从而省去了cpu从内存获取的时间,提高了运行效率
注意事项:
1:只能修饰局部变量,不能修饰全局变量和函数;
2:register 修饰的变量不能通过&获取变量的地址:(C++升级:当用&获取变量地址时,该变量不会保存到寄存器中)
3:register 修饰的变量类型一定是cpu所能接受的数据类型!

const:C语言中,将const修饰的变量变为只读变量。常应用在修饰函数形参里,保护实参不被修改。
C++升级:const修饰的变量就是常量。const地址必须由const指针保存(const对const )
记住两点 : 1 常量 2 .上面升级的整句话

typedef:提高代码移植性 提高编码效率 给数据类型重命名;
思考 C语言:使用typedef如何重命名函数指针;
C语言 typedefn int (* P_FuNC)(int,int); C++升级:using P_FUNC2 = int(*)(int ,int );

auto:自动变量:强调变量的生命周期
C++升级:auto:类型推导符,目标:现代化标称(提高开发效率) 很强!!!!!!


malloc 实现原理?free 如何知道要释放多少内存空间? C++ new delete
malloc是按照字节为单位分配,new按照数据类型为单位分配空间;
malloc是函数,new是运算符;
malloc只分配空间,不初始化,new既可以分配空间又可以初始化。


一维数组名的作用:保存数据首个元素的地址
二维数组名的作用:保存数据首个一维数组的地址
三维数组名的作用:保存数据首个二维数组的地址

2.引用,类和结构体,string类,

2.1 函数实参

什么时候传值?什么时候传地址?

  • 当只使用不修改实参变量的值,传实参变量名;
  • 当既使用又修改实参变量的值,传实参变量对应空间的地址;

2.2 引用

参考链接:https://www.jianshu.com/p/d19fc8447eaa
C98标准都是左值引用;
简单判定左值与右值方法: 能取地址便是左值,不能则是右值;
右值引用的符号是 && 左值引用的符号是 &;

1.引用的注意事项:

  • ①引用必须在定义的同时初始化变量;
  • ②只能绑定到一个变量;
  • ③当用const限制一个引用变量时,需要 const对const;

2.引用可以作为形参,解决了传参和传地址;

3.作返回值时函数调用可作为左值;

4.引用是否占用内存空间?

  • 从使用角度看是不占内存的;
  • 从编译器角度的确给分配了空间,32位 4字节 64位 8字节;

5.面试问题:指针和引用的区别;

  • ①不存在空引用(保证不操作空指针),
  • ②必须初始化(保证不是野指针),
  • ③一个引用永远指向他初始化的那个对象(保证指针值不变)。

也可以查阅链接:https://blog.csdn.net/boy_of_god/article/details/81022316

6.面试问题:为什么会出现引用?
结论:直接的原因是为了支持运算符重载
也可以查阅链接:https://www.cnblogs.com/lfri/p/12695938.html

7.左值引用,右值引用;左值右值如何判断?

8:引用:什么时候加const? 什么时候不加const?
也可以查阅链接:https://blog.csdn.net/sammie123321/article/details/96426851

#include <iostream>
using namespace std;
//引用可以作形参
void func1(int &a,int &b)
{
    int temp = 0;
    temp = a;
    a = b;
    b = temp;
}
//引用作为返回值 函数可以作左值
int & func2(const int &a)
{
    int num = 5; //此时会警告,返回一个局部变量,改变如下注释:
    //static int num = 5;
    return num; 
)
int main(void)
{
    int a = 5,b = 6;
    func1(a,b)
    cout << a << "," << b << endl;
    func2(a) =10;
    cout << a << endl;
    
    int num = 5;
    int &l_num = num; 
    //int &l_num = 5;//测试左值引用绑定左值,报错
    //static int &l_num = 5;解决左值错误问题;
    int &&r_rnum = 5;//右值引用绑定右值
    //int &&r_rnum = num;//测试右值引用绑定左值,失败,报错;
    //int &&r_rnum = std::move(num);//右值改成左值 成功
    //但这里发生了 对象移动 
    // 对象移动解决的是对象拷贝的开销问题
    return 0;
}

2.3 C++ 对 C 的函数扩展

1.时间换空间,空间换时间

  • 时间:运行时间,编译时间;
  • 空间:内存空间;

也可以查阅链接:https://blog.csdn.net/cui929434/article/details/98033039

2.用编译时间换内存空间?(宏函数)

#define MAX(a,b) ((a) > (b)) ? (a) : (b)//傻瓜式替换,预处理阶段
//如上增加了编译时间

//如下增加了内存时间
int max_num(int a,int b)
{
    return a > b ? a : b;
}

3.用内存时间换内存空间?

  • C语言:
    (inline(只能修饰函数C99)内联函数:将函数体的代码段内嵌到函数调用的地方)
    inline:将代码内嵌到函数调用的地方,省去了函数调用返回的过程,提高了程序运行效率;
    C语言下只要声明为inline(内敛),一定会一内联的方式进行处理

  • C++升级:
    (编译器会判断:循环,静态,递归,异常处理;包括这些都不会实现)
    什么时候使用inline? 功能简单且代码短小,但频繁调用!
    // 5 行代码以内吧,一定相信存在必定有用!!!!

  • 使用注意事项:
    调用inline内联函数之前必须声明或者定义该函数

void func(); //不声明直接使用func()会报错,必须提前声明

inline void func(){}

内联函数作用:

  • ① 内联函数在编译的时候将不进行函数调用;
  • ② 编译器将内联函数的代码粘贴在调用(形式上调用)处,可以提高效率;
inline max_num(int a,int b)
{
    return a > b ? a : b;
}
int main()
{
    max_num(5,3);
    max_num(3,4);
}

4.默认参数:

  • 函数默认参数:函数定义时给形参指定值
  • 默认参数规则:当前参数的右边参数必须有默认参数
  • 函数占位参数:预留参数接口,函数调用时必须指定参数;

5.重载
函数重载条件?

  • ① 函数名可以相同,函数形参不同;
  • ② 形参个数相同,但是类型不同;
  • ③ 形参个数不同;

注意事项:注意默认参数对重载条件的影响;

void add(int a,int b = 5){}//报错,出现二义性;
void add(int a){}

2.4 类和结构体

1.结构 的升级:

  • C语言:
    内部只能通过函数指针去访问外部函数;
  • C++升级:
    ①定义变量(结构体名+变量名)
    ②结构 可以保存函数
    ③结构 可以继承
    ④结构 可以多态
    ⑤结构 可以封装(权限修饰符:public,pravite,protect 控制访问权限)
    结构 默认访问权限是public;
    类 默认访问权限是private;

    //private 不能通过变量外界访问,但可以在内部函数中访问

2.class vs struct
C++新的数据类型:class;基本上完全等同于结构体;

  • 语法上唯一一个区别:class默认权限是private,struct默认权限是public;

  • 叫法区别:
    ①class类,struct结构体;
    ②class定义的变量称之为对象,struct称之为变量;
    ③class里保存得变量称之为属性或者成员变量;保存的函数称之为方法;

#include <iostream>
using namespace std;

struct Node
{
    int num;
public:
    void test()
    {
        cout << "func test" << endl;
    }
}; 

struct Nodes : public Node
{
    void test1()
    {
        cout << "func test1" << endl;
    }
};

int main()
{
    Node p;
    p.test();
    Nodes ps;
    ps.test();
    return 0;
}
#include <iostream>
using namespace std;
class Student
{
public:
    int mNum;
    char mName[20];
    int mage;
   /*void eat()//类内实现,有可能被内联,消耗内存
    {
        cout << "eat" << endl;
    )
    */
    void eat();//外部实现需要声明
};
void Student::eat()
{
    cout << "eat" << endl;
}
int main(void)
{
    return 0;
}

举一个将类写进头文件的例子:
func.h

#pragma once//防止头文件重复包含

#include <iostream>
using namespace std;
class Student
{
public:
    void eat()
    {
        cout << "eat" << endl;
    }
};

func.c

#include <iostream>
#include "func4.h"
using namespace std;
int main(void)
{
    Student p;
    p.eat();    
    return 0;
}

2.5 string 类:

1.介绍
std提供的标准字符串处理的类,class string;
特点:可变长(动态分配)(所以不用关心长度)

#include <iostream>
#include <string>
using namespace std;
int main(void)
{
    //string的定义
    string s = "hello world";//s叫做对象
    string t = "hahhahaha";
    string s2 = s + t;//strcat

    string s3("hello world");// 构造函数初始化的方式
    string s4(10,'a');
    cout << s3 << endl;
    cout << s2 << endl;
    cout << s4 << endl;
    return 0;
}

2.string 的属性

  • ①:size(): string的大小
  • ②:empty(): 判断是否为空; bool
  • ③:capacity():返回的是string当前的容量
  • ④:resize():重置大小
#include <iostream>
#include <string>
using namespace std;
int main(void)
{
    string s("helloworld");
    // 两种方法无区别测长度,但建议使用size();
    cout << "size1=" << s.size() << endl;//no '\0' 
    cout << "size1=" << s.length() << endl;//no '\0'
    return 0;
}
#include <iostream>
#include <string>

using namespace std;
int main(void)
{
    string s("helloworld");
    cout << "size1=" << s.size() << endl;//no '\0' //jianyi yong 
    cout << "size1=" << s.length() << endl;//no '\0'
    cout << "is empty:" << s.empty() << endl;
    //判断是否为空,empty()
    cout << "capacity:" << s.capacity() << endl;
    s.resize(100);
    //resize重置大小,但未修改字符串
    cout << "capacity:" << s.capacity() << endl;
    s.resize(100,'a'); //memset
    //重置大小并用 a 填充
    cout << "capacity:" << s.capacity() << endl;
    cout << s << endl;
    string s1;
    string s2 = "";
    cout << "is empty:" << s1.empty() << endl;
    cout << "is empty:" << s2.empty() << endl;

    return 0;
}

3. string 输入输出

#include <iostream>
#include <string>
using namespace std;

int main(void)
{
    string s;
    //cin.getline(s,100);//报错,实参string 形参char,类型不兼容
    //cin >> s; // 获取单词用这种
    getline(cin,s);//获取句子用这种
    cout << s << endl;
    string src;
    strcpy(src,s.c_str());
    cout << s << endl;
    
    return 0;
}

4.string 的遍历

#include <iostream>
#include <string>
using namespace std;
int main(void)
{
    string s("hello world");
    for(int i = 0;i<s.size();i++)
    {
        //cout << s[i] ;
        cout << s.at(i) << endl;
        //[] VS at : []如果越界不报错,at越界产生异常
    }
    /*
    for(auto temp :s)
    {
        cout << temp << endl;
    }
    */
    //迭代器:指针(内部指针);  
    return 0;
}

5.迭代器iterator:指针(内部指针)
三种:正向,反向:const

#include <iostream>
#include <string>
using namespace std;

int main(void)
{
    string s("hello world");

    //string :: iterator begin_it = s.begin();
    //string :: iterator end_it = s.end();
    for(auto it = s.begin();it != s.end();it++)
    {
        cout << *it << endl;
    }
    //string::const_iterator it = s.cbegin();
    for(auto it = s.cbegin();it!=s.cend();it++)
    {
        cout << *it << endl;
    }
    //string::reverse_iterator it = s.rbegin();

    for(auto it = s.rbegin();it != s.rend();it++)
    {
        cout << *it << endl;
    }
    return 0;
}

自学网站:https://en.cppreference.com/w/
6.string 赋值
7.string 连接
8.string 比较
9.string 类的子串(比较重要,可以借助手册网站)
10.string 交换

11.下面是最重要最常用的
查找:最好去官网看源码:

#include <iostream>
#include <string>

using namespace std;
int main(void)
{
    string const s = "this is a string";
    std::string::size_type n;
    n = s.find('w');
    cout << n << endl;
    if(n == string::npos)
    {
        cout << "not found" << endl;
    }

    return 0;
}
查找总结:
str.find_first_of(查找目标字符串中任意一个字符在str里第一次出现的位置)
str.find_last_of(查找目标字符串中任意一个字符在str里最后一次出现的位置)

str.find_first_not_of(s)(str中第一个不是目标字符串任意一个字符的位置)
str.find_last_not_of(s)(str中最后一个不是目标字符串中任意一个字符的位置)

12.替换 自学

13.删除 迭代器

#include <iostream>
#include <string>

using namespace std;
int main(void)
{
    string s = "abdcd";
    //s.erase(1;
    //s.erase(1,1);
    auto it = s.erase(s.begin()+1);
    cout << *it << endl;
   //cout << s << endl;
    return 0;
}

14.插入 自学
用迭代器!!!

15.容器, 删除

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main(void)
{
    vector<int> v1={1,2,3,4,5};
    vector<int> v2;
    vector<string> v3;
    v3.push_back("hello");
    //v3.push_back("world");
    //bianli
    /*for(auto it = v1.begin();it!=v1.end();it++)
    {
        cout << *it << endl;
    }
    */
    //shanchu
    v1.erase(v1.begin()+1);
    //cout << v3 << endl;
    for(auto it = v1.begin();it!=v1.end();it++)
    {
        cout << *it << endl;
    }
    return 0;
}

课后习题

字符串I am from shanghai 分别用 C/C++ 实现逆序打印 输出:shanghai from am i;



3.7月21日自学 类和对象

基于《面向对象的嵌入式软件开发》一书。

一,构造函数的几点说明

① 构造函数的名称必须和类的名称相同
② 构造函数不能有返回值,函数体中不能有return语句
③ 构造函数在定义对象时会自动执行,不需手动调用

注意事项:

1.构造函数的调用是强制性的,不调用是错误的;
2.创建对象时只有一个构造函数会被调用,而且构造函数只会在对象刚被创建时被调用。
3.如果用户没有定义构造函数,那么编译器会自动生成一个默认的无参构造函数,
  只是这个构造函数的函数体是空的,也不执行任何操作;
4.如果没有定义的拷贝构造函数,编译器也会自动生成一个默认的拷贝构造函数,进行简单数据的赋值。
5.一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。
  一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。

二.引用

① 为了避免进行数据的拷贝,最好使用引用;
② 为了避免实参数据被修改,最好使用常引用;

三.析构函数

① 析构函数没有参数,不能被重载
② 一个类只能有一个析构函数,要么用户自己定义,要么编译器自动生成一个默认的析构函数
③ 析构函数在对象被销毁时调用,而对象的销毁时机与它所在的内存区域有关;

四.对象初始化列表

注意事项

① 初始化列表要优先于当前对象的构造函数先执行;
② 子对象的初始化顺序和其在初始化列表的排列顺序无关,但和在类中的声明顺序有关,先声明的先初始化
③ 析构函数的调用顺序与构造函数相反;
④ 参数初始化表还有一个很重要的作用,那就是初始化const成员变量,
  初始化const成员变量的唯一方法就是使用参数初始化表。

对于上述不足的点补充:
问:对象初始化列表什么时候使用?
https://blog.csdn.net/wy1550365215/article/details/77930637

五.编译器对属性和方法的处理机制

问:一个对象所占内存大小如何计算呢?如下举例说明:

#include <iostream>
using namespace std;
class Test
{
public:
    Test(int a,int b)
    {
        m_a = a;
        m_b = b;
    }
    void print()
    {
        cout << "a=" << m_a << ",b=" << m_b << endl;
    }
    static void printS()
    {
        cout << "c=" << m_c << endl;
    }
private:
    int m_a;
    int m_b;

    static int m_c;
};
int main(void)
{
    printf("size=%d\n",sizeof(Test));
    Test t1(1,2),t2(3,4);
    t1.print();
    t2.print();

    return 0;
}

测试结果:
在这里插入图片描述
size=8 表明是两个整型字节数大小。
其实C++类对象中的成员变量和成员函数是分开存储的;

① 普通成员变量:存储在对象中,与struct变量具有相同的内存布局和字节对齐方式。
② 静态成员变量:存储在全局数据区中。
③ 成员函数:存储在代码段中。

计算大小时不是很好理解,比如:
问:很多对象共用一块代码,代码是如何区分具体的对象?
这里涉及编译器对类中成员函数的处理。类在C++内部是用结构体来实现的?

#include <iostream>
using namespace std;
struct Test
{
    int m_a;
    int m_b;
};
static int m_c = 10;

void Test_init(Test* const p,int a,int b)
{
    p->m_a = a;
    p->m_b = b;
}
void Test_print(Test* const p)
{
    cout << "a=" << p->m_a << ",b=" << p->m_b << endl;
}
void Test_printS()
{
    cout << "c=" << m_c << endl;
}
int main(void)
{
    Test t1;
    Test_init(&t1,1,2);

    Test t2;
    Test_init(&t2,3,4);

    Test_print(&t1);
    Test_print(&t2);
    return 0;
}

如上代码模拟编译器的内部处理,以帮助更好理解 类,并不是编译器内部的真正实现。
由以上的转换,可以看出:

① C++类对象中的成员变量和成员函数是分开存储的,C语言中的内存四区模型仍然有效。
② C++中类的普通成员函数都隐式地包含一个指向当前对象的常指针,
  在上面代码的转换中就是指针p,通过这个常指针可以知道当前操作的是哪一个对象。
③ 静态成员函数,成员变量属于类

静态成员函数与普通成员函数的区别:

① 静态成员函数不包含指向具体对象的指针。
② 普通成员函数包含一个指向具体对象的指针。

六.this指针

类的普通成员函数都有一个指向当前对象的常指针,C++的关键字this就是那个指针
功能:代表当前操作对象。

七 友元函数 和 友元类

1.友元函数:当前类的外部定义,不属于当前类的函数也可以在类中声明,但要在前面加关键字friend,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。友元函数可以访问当前类中的所有成员,包括public,proteted,private等属性的成员;

如何将非成员函数声明为友元函数呢?

#include <iostream>
using namespace std;

class Student
{
    friend void show(Student *pstu); 
public:
    Student(char *name,int age,float score):m_name(name),m_age(age),m_score(score)
    {

    }
private:
    char *m_name;
    int m_age;
    float m_score;
};

void show(Student *pstu)
{
    cout << pstu->m_name << "year is " << pstu->m_age << ",score is " << pstu->m_score << endl;
}

int main()
{
    Student stu("ming",15,90,6);
    show(&stu);

    Student *pstu = new Student("li",16,80.5);
    show(pstu);
    return 0;
}

而友元函数不能 直接 访问类的成员,必须借助对象;

2.友元类
有缘类中的所有成员函数都是另外一个类的友元函数。

3.友元函数的几点注意事项:

① 友元函数的声明与位置,public,private等都无关,可以在类的内部任意位置声明友元函数。
② 友元函数不是类的内部函数。
③ 友元的关系是单向的而不是双向的,如果声明了类B是类A的友元类,
  不等于类A是类B的友元类,类A中的成员函数不能访问类B中的private成员。
④ 友元的关系不能传递,如果类B是类A的友元类,类C是类B的友元类,不等于类C是类A的友元类。
⑤ 友元函数会破坏类的封装性,不是迫不得已,尽量少用友元函数。

4.初识C++封装

一.C++中的四种强制类型转换符

四种目前先学两种,static_cast,const_cast;其他了解看如下链接https://www.cnblogs.com/linuxAndMcu/p/10387829.html#_label0
① static_case(静态转换)
② const_cast(常量转换)
③ reinterpret_cast(不相关类型的转换)
④ dynamic_cast(动态转换)

  1. static_case(静态转换)
使用场景:
- 基本数据类型之间转换,但安全性问题由程序员自己把握;
- 在有类型指针 与 void* 之间转换 (目前是遇到这种情况的时候强转)
- 用于 类 层次结构中 基类 和 派生类 之间 指针 或 引用 的转换
	上行转换(子类--->父类) 安全;
	下行转换(父类--->字类) 不安全;		//由于没有动态类型检查,所以是不安全的;

问:什么是动态类型检查 ?C++如何实现 ? 使用场景 ?
出现了很多暂时看不懂的名词,先放一放。
https://blog.csdn.net/yuanzhangmei1/article/details/11962887
问:静态类型和动态类型有什么区别?
发生在不同语言的类型比较上,和现阶段学习的知识不符合,但可以了解一下
https://www.jianshu.com/p/bc492fcbf18f

② 使用特点:

  • 主要执行非多态的转换操作,用于代替C中通常的转换操作。
  • 隐式转换都建议使用 static_cast 进行标明和替换。
  • 不能使用 static_cast 在有类型指针内转换。翻译:指针两者之一必须是 void* 才能转换

③ 编程实例:
在这里插入图片描述

#include <iostream>
using namespace std;

class CBase //基类(父类)
{

};

class CDerived : public CBase //派生类(子类)
{

};

int main()
{
	//1.使用static_cast在基本数据类型之间转换
	float fval = 10.12;
	int ival = static_cast<int>(fval); // float-->int
	cout << ival << endl; //out:10;

	//2.使用static_cast在有类型指针与void* 之间转换
	int* intp = &ival;
	void* voidp = static_cast<void*>(intp); // int*--->void*
	//cout << *voidp << endl; //error,voidp的大小未知
	long* longp = static_cast<long*>(voidp);
	cout << *longp << endl; // out:10

	//3.用于类层次结构中基类和派生类之间指针或引用的转换
	//上行转换(派生类--->基类)是安全的
	CDerived* tCDerived1 = nullptr;
	CBase* tCBase1 = static_cast<CBase*>(tCDerived1);
	//下行转换(基类--->派生类)由于没有动态类型检查,所以是不安全的
	CBase* tCBase2 = nullptr;
	CDerived* tCDerived2 = static_cast<CDerived*>(tCBase2);//不会报错 但是不安全

	// 不能使用static_cast在有类型指针内转换
	float* floatp = &fval; //10.12的addr
	//int *intp1 = static_cast<int *>(floatp); 
	//error,不能使用static_cast在有类型指针内转换
	cout << *floatp << endl; //out : 10.12

	return 0;
}

2.const_cast:(常量转换)

  • 使用场景
    常量指针(或引用)与非常量指针(或引用)之间的转换。
  • 使用特点:
    cosnt_cast 是四种类型转换符中唯一可以对常量进行操作的转换符。
    去除常量性是一个危险的动作,尽量避免使用。
  • 实例
#include <iostream>
using namespace std;

int main()
{
	int value = 100;
	const int* cpi = &value; // 定义一个常量指针
	//*cpi = 200;   // 不能通过常量指针修改值 常量指针可以修改指针
	cout << *cpi << endl;
	
	// 1. 将常量指针转换为非常量指针,然后可以修改常量指针指向变量的值
	int* pi = const_cast<int*>(cpi); //将cpi的类型从 const int* 强转成 int*
	// const 对 const 
	*pi = 200;
	cout << *pi << endl;
	// 2. 将非常量指针转换为常量指针
	const int* cpi2 = const_cast<const int*>(pi); // *cpi2 = 300;  //已经是常量指针

	const int value1 = 500;
	const int& c_value1 = value1; // 定义一个常量引用 //左值引用绑定左值
	//如果能取地址 就是左值; 不能取地址就是右值;
	// 3. 将常量引用转换为非常量引用
	int& r_value1 = const_cast<int&>(c_value1);

	// 4. 将非常量引用转换为常量引用
	const int& c_value2 = const_cast<const int&>(r_value1);
}

二.C++封装

问:如何体现封装解决代码的维护性 ?
简答:保证代码的独立性,从而提高代码的维护性
问:独立性的好处? 封装如何保证独立?
简答:通过类的权限修饰符限制访问权限。pubulic,private,protect

三.构造函数

特点:
①.自动调用(实例对象时,自动调用构造函数)
② 构造函数的函数名与类名一致;
③ 构造函数没有返回值
④ 构造函数可以重载;
⑤ 当类中无任何构造函数时,系统会默认生成一个无参的构造函数
但凡有一个构造函数,编译器都将不再生成构造函数。

构造函数的种类:
①- 无参构造函数;
②- 有参构造函数;
③- 拷贝构造函数;
形参是该类型的对象引用:若类里无自定义拷贝构造函数,系统会默认生成拷贝构造函数。
深拷贝和浅拷贝(默认生成 的拷贝都是浅拷贝)(多个成员指向了同一块空间)
(多个成员指向不同的空间)(只有类里有指针才会涉及到深拷贝和浅拷贝)
④- 移动拷贝构造函数;
解决对象拷贝的开销问题;尤其是临时对象;
对象移动:将该对象移动到目标对象的空间(获取目标对象的空间使用权)
匿名对象:https://www.cnblogs.com/cthon/p/9173472.html
//移动拷贝构造函数

Test(Test &&other) noexcept
{
    cout << "move copy Test" << endl;
    this->m_ptr = other.m_ptr;
    other.m_ptr = nullptr;
}

⑤- 类型转换构造函数;
explicit:防止编译器发生隐式转换
补充:C++11 引入的两个关键字,delete default
例如

class A
{
    public:
    //A()=delete;//告诉编译器不生成默认无参构造函数
    //A()=default;//告诉编译器自动生成无参构造函数
};

如何写好构造函数 ???
https://blog.csdn.net/farsight2009/article/details/4601396

四…析构函数

特点:
- 无返回值
- 规定函数名:~类名
- 无参
- 不能重载
- 当对象离开所在作用域释放空间时先调用析构函数。
- 一个类只有一个

五.this指针

1.thisi指针:每实例化一个对象都有一个指向该对象的指针(this)
2.对象模型:每个对象都有自己独立的属性(成员变量)空间,共享代码空间(方法,函数);
3.this指针的作用:区别同一个类定义的不同实例

六.初始化列表

1.定义并初始化(先于构造函数),初始化效率高于构造函数初始化(不用发生函数调用)
2.什么情况下会使用初始化列表?https://blog.csdn.net/wy1550365215/article/details/77930637

  • 非静态常量数据成员的初始化
  • 引用成员的初始化
  • 初始化没有默认构造函数的成员对象
  • 基类数据成员的初始化
  • 当构造函数的参数名字与数据成员的名字相同时
  • 性能原因

自己写二代初始化列表例子: 因为学的时候函数末尾忘记加分号 一直报错没发现。
然后写了构造函数,发现根本不会调用构造函数。

#include <iostream>
using namespace std;
class A
{
public:
	int m_a, m_b;
	A(int a, int b) :m_a(a), m_b(b) {};
	void print()
	{
		cout << m_a << "," << m_b << endl;
	}
};

int main(void)
{
	A a(5, 6);
	a.print(); //输出5,6 正确;
	return 0;
}

引用成员的初始化示例:

//int& r_num 课上提问答错了,必须记住!
class B
{
public:
    int B_num;
    B b;
    //int& r_num;//不注销报错,原因是左值引用只能绑定左值
    B () : B_num(0),b(5)//,r_num(0)
    {};
    B(int num)
    {
        this->B_num = num;
    }
};

七 运算符重载

//运算符重载函数:=
Test &operator=(const Test &other)
{
     cout << "operator =" << endl;
     int len = strlen(other.m_ptr);
     this->m_ptr = new char[len + 1];
     strcpy(m_ptr, other.m_ptr);
     return *this;
}

一. 类

在构建一个类的时候,有些成员函数必定会写:
构造函数,析构函数,运算符重载,set/get方法,普通成员函数,静态成员函数
所以可能用会用到关键字:static,const,mutable,
其中关键字 static 的作用:
用来声明静态变量。a.修饰局部变量——>会提升局部变量的生存周期
b.修饰全局变量——>作用域被限制——>被限制到当前的原文件中
c.修饰函数——>作用域被限制——>被限制到当前的原文件中

1.关键字 static
①.静态成员:属于类,不属于类的对象(不存在对象的空间中,但被所有对象共享)
注意事项:
不能在构造函数中初始化,只能在类外初始化
权限修饰符会影响静态成员

class Test
{
public:
    static int m_num;
    int m_count;
private: 
    //static int m_connt //错误,权限修饰符会影响静态成员    
};
int Test::m_num=5; //类外初始化
int main()
{
    Test t1;
    cout << sizeof(t1) <<endl;//检测静态成员是否所属该类
    Test t2; 
    t2.m_count = 10;
    cout <<t1.m_count << endl; //输出10,检测静态成员共享成功
}

②.静态成员函数:属于类,不属于对象(被所有对象共享,没有this指针意味着不能访问非静态成员)

class Test
{
public:
    static int m_num;
    int m_count;
    static void print()
    {
        //cout << m_count << endl;//错误,访问非静态常量
        cout << m_num << nedl;//正确,只能访问静态成员
    }
};

//普通成员方法的函数名不是函数的入口地址,无法作为参数传递;
//static修饰的方法的函数名就是函数的入口地址,可以作为参数传递;

2.问题:

c/c++中static关键字的作用 ?
C语言:
https://www.cnblogs.com/ustc-anmin/p/11239257.html
C++升级后:
https://www.cnblogs.com/songdanzju/p/7422380.html

5.重载,简学多态

二.友元 (少用)

友元的作用:提高程序运行效率(让类的非公有函数可以直接访问类的非公有成员,
省去了函数调用的过程)
友元的缺点:破坏了C++ 的封装性!
友元的种类:友元函数,友元类,友元成员函数

1 友元函数:
意义:是指某些虽然不是类成员却能够访问类的所有成员的函数。
形式: friend 类型名 友元函数名(形参表);
注意:
① 函数必须在类中说明,说明可以出现在类的任何地方,包括在 private 和 public 部分;
② 类外定义。

2 友元类:
友元类的所有成员函数都是另一个类的友元函数,
都可以访问另一个类中的隐藏信息.(包括私有成员和保护成员)
注意事项:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。同样要看类中是否有相应的申明。

1,2 写个简单例子

/* 形参很烦
友元函数的使用
因为友元函数没有this指针,则参数要有三种情况:
1.要访问非static成员时,需要对象做参数;
2.要访问static成员或全局变量时,则不需要对象做参数;
3.如果做参数的对象是全局对象,则不需要对象做参数.
4.可以直接调用友元函数,不需要通过对象或指针
*/
class One
{
public:
	//友元类的声明,表示 two索取one的内容
	friend class two; 

	//该函数是友元函数的声明
	friend void print(int num,One& a);
private:
	int m_num;
};
//友元函数能访问到类中所有成员
void print(int num, One& a)
{
	a.m_num = num;
	cout << a.m_num << endl;
}
class Two
{
public:
};
int main(void)
{
	One a;
	print(1, a);

	return 0;
}

3.友元成员函数
// 使类B中的成员函数成为类A的友元函数,
// 这样类B的该成员函数就可以访问类A的所有成员了。
// 一般的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。
// 另一方面,不必预先声明类和非成员函数来将它们设为友元。

class A;    
//当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。这是类A的声明
class B
{
public:
	void set_show(int x, A& a);  //该函数是类A的友元函数
};

class A
{
public:
	friend void B::set_show(int x, A& a);  //该函数是友元成员函数的声明
private:
	int data;
	void show() { cout << data << endl; }
};
void B::set_show(int x, A& a)      
//只有在定义类A后才能定义该函数,毕竟,它被设为友元是为了访问类A的成员
{
	a.data = x;
	cout << a.data << endl;
}
int main(void)
{
	class A a;
	class B b;
	b.set_show(1, a);
	return 0;
 }

三.运算符重载

这几个运算符不能进行重载 “::”, "?: “, “.”, " .*” , “sizeof”
简单实现运算符重载例子:

#include <iostream>
#include <string.h>
#include <string>

using namespace std;

using p_func = void(*)(void);

class Test
{
public:
    int m_num;
    string m_s;
    char *m_ptr;

    Test()
    {
        m_ptr = new char[100];
    }

    Test(int num, string s, char *ptr) : m_num(num), m_s(s)
    {
        int len = strlen(ptr);
        this->m_ptr = new char[len + 1];
        strcpy(m_ptr, ptr);
    }

    ~Test()
    {
        delete[] m_ptr;
    }

    static void print()
    {
        cout << "hello world" << endl;
    }

    Test &operator=(const Test &other)
    {
        if (this != &other)
        {
            this->m_num = other.m_num;
            this->m_s = other.m_s;
            int len = strlen(other.m_ptr);
            this->m_ptr = new char[len + 1];
            strcpy(m_ptr, other.m_ptr);
        }

        return *this;
    }

    bool operator>(const Test &t)
    {
        return this->m_num > t.m_num;
    }
    
    bool operator<(const Test &t)
    {
        return this->m_num < t.m_num;
    }

    friend ostream &operator<<(ostream &out, const Test &t)
    {
        out << "num = " << t.m_num << endl;
        out << "s = " << t.m_s << endl;
        out << "ptr = " << t.m_ptr;

        return out;
    }

    friend istream &operator>>(istream &in, Test &t)
    {
        in >> t.m_num;
        in >> t.m_s;
        in >> t.m_ptr;

        return in;
    }
    
    operator int()
    {
        return this->m_num;
    }

    operator char*()
    {
        return this->m_ptr;
    }

    operator p_func()
    {
        return print;
    }

    //i++ ,++i;  //实现

    //()函数运算符 --  函数对象


};

int main()
{
    string s = "hello world";
    Test t1(1, s, "zhangsan");
    Test t2;
    t2.operator=(t1);

    //cout << s << "hello" << endl;
    //cout.operator<<(cout,t2);
    //operator<<(cout,t2);
    cout << t2 << "hello" << endl;

    Test t3;
    cin >> t3;

    cout << t3 << endl;

    if(t2 > t3)
    {
        cout << "t2 > t3" << endl;
    }

    int num = static_cast<int>(t3);
    int num2 = static_cast<int>(t2);
    cout << num << endl;
    cout << num2 << endl;

    string s2 = static_cast<string>(t3);
    char *ptr = static_cast<char *>(t2);
    //cout << s2 << endl;
    cout << ptr << endl;

    p_func f = static_cast<p_func>(t2);
    f();
    
    for(int i = 0; i < 5 ; ++i)
    {

    }

    
    return 0;
}

四 .组合 与 继承

1.组合定义:在新类里面创建原有类的对象,重复利用已有类的功能。(has-a关系)

2.继承定义:可以使用现有类的功能,
并且在无需重复编写原有类的情况下对原有类进行功能上的扩展。(is-a关系)

简单写一个实例表示:

class A
{
public:
private:
};
class B
{
public:
	A a;//B里有A的对象,就是组合
private:
};
class C : public B //继承
{
public:
};
int main(void)
{
	return 0;
}

3.问题:继承规则
有public、private、protected三种
(它们直接影响到派生类的成员、及其对象对基类成员访问的规则)
① public(公有继承):
继承时保持基类中各成员属性不变,并且基类中private成员被隐藏。
派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;
派生类的对象只能访问基类中的public成员。

② private(私有继承):
继承时基类中各成员属性均变为private,并且基类中private成员被隐藏。
派生类的成员也只能访问基类中的public/protected成员,而不能访问private成员;
派生类的对象不能访问基类中的任何的成员。

③ protected(保护性继承):
继承时基类中各成员属性均变为protected,并且基类中private成员被隐藏。
派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;
派生类的对象不能访问基类中的任何的成员。

父类私有 绝对不可被继承的子类所使用

4.什么时候用组合,什么时候用定义
目前简单用 has-a 包含关系 判断用组合,is-a 属于关系 判断用继承
https://blog.csdn.net/niuyisheng/article/details/9734921

五.多态

心得:多态性还有动态绑定很好用,:多态性是指不同类的对象对同一消息的不同回应,

我脑海里出现了一个场景:
我妈喊吃饭的时候,我爸可以慢悠悠下楼,我却是不可以的(慢了会被我妈念叨)

要想在程序中实现多态(动态绑定)得几个条件:
1.弄明白类继承图
2.避免例子中Base d=b这种错误,要通过父类的指针或者引用调用虚函数
3.指针的指向对象不能弄乱。

父类和子类之间互相赋值的类型兼容原则,支持向上类型转换。
作用:不会发生类型变化(只有继承的情况下,且不支持向下类型转换)
意义:实现多态的前提条件

利用了虚函数,暂时不太懂,不过没事
注意事项:类外实现不再加virtual;如果担心函数遮蔽,就在虚函数名字后面添加 override ;
https://www.cnblogs.com/alinh/p/9636352.html

#include <iostream>
using namespace std;
class A
{
public:
    int m_a;
    A(int a):m_a(a) //初始化列表
    {
        cout << "A()" << endl;
    }   
    virtual void print()  //虚函数
    {
        cout << "A" << endl;
    }
    virtual ~A()//析构函数
    {
        cout << "~A()" << endl;
    }
};
class B : public A
{
public:
    int m_b;
    B(int b) : m_b(b),A(5)
    {
        cout << "B" << endl;
    }
    virtual void print() override
    {
        cout << "B" << endl;
    }
    ~B()
    {
        cout << "~B()"<< endl;
    }
};
class C : public A
{
public:
    int m_c;
    C(int c) : m_c(c),A(5) {}//初始化列表
    virtual void print()
    {
        cout << "C" << endl;
    }
    ~C(){}
};

class D : public A
{
public:
    int m_d;
    D(int d) : m_d(d),A(5){}
    virtual void print()
    {
        cout << "D" << endl;
    }
    ~D(){}
};
//同一接口,传递不同的实例,执行不同操作!
// 如果没有虚拟函数,没有函数遮蔽,测试函数要写的如下所示,很麻烦
// void test(A a)
// {
//     a.print();
// }

// void test(B b)
// {
//     b.print();
// }

// void test(C c)
// {
//     c.print();
// }

void test(A &pa)
{
    pa.print();
}
//同一接口,传递不同的实例,执行不同操作!
//多态的条件:1、继承 2、发生函数遮蔽 3、虚函数
//多态的发生时机:父类的指针或者是引用指向子对象;
//多态的意义:可以在任意地方用子类替换父类;
//多态的作用:提高代码的扩展性;
(添加新的功能时,不修改原来的代码,只添加新的代码(开闭原则))
//多态实现的情况下,父类的析构函数必须是虚析构函数?
 如果不是,就不能正常释放子对象的空间?
int main()
{
    //父类和子类之间互相赋值的类型兼容原则:支持向上类型转换
    // A a(5);
    // B b(10);
    // C c(15);
    // D d(20);
    // test(a);
    // test(b);
    // test(c);
    // test(d);
    // A *pa = &a;
    // B *pb = &b;
    // C *pc = &c;
    // a = static_cast<A>(b);
    //向上转型:不会发生类型变化;(只有继承)  意义:实现多态的前提条件
    // a.print();
    // a = c;
    // a.print();
    // //同一接口,传递不同的实例,执行不同操作!
    // pa = pb;
    // pa->print();
    // pa = pc;
    // pa->print();
    // pb = pa;
    //b = static_cast<B>(a);//不支持向下类型转换(只有继承)
    //B b(10);
    A *pa = new B(10);
    delete pa;
    return 0;
}

PS:后续笔记会一直在本文补充。

猜你喜欢

转载自blog.csdn.net/GameStrategist/article/details/107428835