C/C++基本知识

前言

俗话说工欲善其事必先利其器,C/C++作为编码基础,还是需要多学习了解的

多态

什么是多态

顾名思义,多态就是指多种状态,即同一个方法的行为随上下文而异,当类之间存在层次关系,并且通过继承关联时,就会用到多态。在程序的实现上,通过父类指针调用父/子类的函数,进而实现父类指针的多种形态,简单说,就是在父类与子类中存在同一函数名,参数,返回值的函数,在进行调用时,会根据调用函数的对象的类型执行不同的函数。
多态主要分为两大类:动态多态静态多态,其中静态多态又分为函数重载泛型编程,而动态多态则是虚函数

静态多态

函数重载实质上是一个简单的静态多态,在实际中用的比较多,就不做过多的解释,主要为:同函数名与返回值,根据不同的参数类型、参数数量实现不同的功能,在编译器编译期间完成,如果能找到合适的函数,就调用,如果找不到,就报错。
泛型编程,则是通过函数模板与类模板实现的,通过指定参数类型实现功能实现,其中,STL就是基于泛型编程思想的成果。

动态多态

重中之重,C++的主要特点之一,不一定要用,但一定要了解。与静态多态最主要的区别就是,在程序运行时根据基类指针指向的对象确定到底需要调用哪个虚函数
现在是北京时间15:53,我在考虑晚饭是吃玉米还是牛排,就以晚餐吃什么举个例子:

#include <iostream>
#include <memory>
using namespace std;
class Food
{
public:
	void EatCorn()	{cout << "eat corn" << endl;}
	void EatSteak(){cout << "eat steak" << endl;}
};

class Dinner
{
public:
	virtual void EatDinner(Food& tb) = 0;
};

class Steak :public Dinner
{
public:
	virtual void EatDinner(Food& tb)
	{
		tb.EatSteak();
	}
};

class Corn :public Dinner
{
public:
	virtual void EatDinner(Food& tb)
	{
		tb.EatCorn();
	}
};

int main()
{
	Food tb;
	Dinner* a = new Corn;
	a->EatDinner(tb);

	Dinner* b = new Steak;
	b->EatDinner(tb);

	system("pause");
	return 0;
}

这是一个简单的动态多态的例子,它是在程序运行时根据条件选择到底该调用哪一个函数。代码解读:同函数名,同参数,同返回值,这是基本条件之一。EatDinner()是构成多态的函数,在本例中,参数没有作用,只是为了说明要有同参数。

  • Food类只是作为一个辅助,用来打印选择结果
  • Dinner类是父类
  • Corn子类,在这里表示玉米类
  • Steak子类,在这里表示牛排类
    从上面代码中可看出,父类Dinner中的虚函数没有定义,而是直接=0,其实也是可以定义的,类似例子中这样,叫纯虚函数
纯/虚函数

虚函数,为多态而生,在基类中用virtual声明,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态连接到该函数,而是在动态运行中,根据对象类型的不同而选用不用的函数,称之为动态链接。在虚函数的概念中,有一类特殊的虚函数,叫做纯虚函数,是为了让子类更好的定义(或者是说没有必要在基类中定义主体)

动态多态的条件
  • 基类中必要包含虚函数,并且在派生类中对虚函数重写
  • 通过基类对象的指针或引用调用虚函数
哪些函数不能作为虚函数
  • 友元函数(friend),因为他不是类的成员函数
  • 全局函数
  • 静态成员函数,因为他没有this指针
多态缺陷
  • 效率低:多态机制需要先查询虚表的地址,再查询对应的函数
  • 空间浪费
基类析构函数为虚函数理由

目的:解决内存泄漏的问题
当我们用基类指针 ptr 指向派生类对象的时候,如果基类析构函数没有定义为虚函数,在用delete ptr析构的时候程序就只会调用基类的析构函数而不会调用派生类的析构函数。
而当定义为虚函数的时候,delete ptr的时候会调用派生类的析构函数,派生类析构完成之后会自动调用基类的析构函数。

使用心得

作为外行人,一大堆介绍只会一堆情况–不知道,所以,在这里记录下一些使用心得:

  • 函数重载用的最多,其最大的作用就是简化开发流程,在实际运用中,为了实现同一个功能,可能会遇到输入不同参数的情况,此时,用函数重载较为方便,例如,要实现一个功能,但数据来源不用,实际应用是通过内存传递,但离线测试则需要从本地读取,因此,可利用重载的方式,如下所示,只有一个参数类型不同:
bool U_Grap_Lego(const char *filename, UTechRobotPose &p);
bool U_Grap_Lego(const std::vector<UTechPoint3Df> data, UTechRobotPose &p);
  • 动态多态用的少点,从测试历程中总结,就是不同的类(基类与子类)各有一个虚函数(同名、同参、同返回),程序在执行时,不同的对象类型调用不同的虚函数。

智能指针

智能指针与普通指针最大的区别就是:可自动释放内存,不需要代码释放,如果使用new,则需要free,如果使用malloc,则需要delete,但智能指针的话,就可以只创建即可,在平时的工作中用的的不多,就简单的介绍下,智能指针主要有四个:auto_ptrunique_ptrshared_ptrweak_ptr

  • auto_ptrC++98的产物,C++11已经放弃,理由很简单,就是不够安全,会造成潜在的内存崩溃问题。
  • unique_ptr
  • shared_ptr多个指针可指向相同对象,该对象及资源会在最后一个引用被销毁的时候释放;其缺点是:当两个对象互相使用一个shared_ptr成员变量指向对方时,会带来循环引用,造成引用计数失效,从而导致内存泄漏
  • weak_ptr协助shared_ptr工作,避免循环引用问题的出现

友元函数

友元函数,关键词为friendbug一样的存在,C++的类本就是为了封装起到保密的作用,但友元函数/类的存在就是开了一个漏洞,让类外的函数可以访问私有成员与保护成员。
友元函数虽然是在类中声明,但它却不是成员函数,所以,他可以放在private中,也可以在public中,因此,一切与成员函数有关的特性都与他无关。
介绍下友元类的一些特点:

  • 友元关系不能被继承
  • 友元关系是单向的,不具有交换性:若类B是类A的友元,则类A不一定是类B的友元,要看在类中是否具有相应的声明
  • 友元关系不具有传递性:若类B是类A的友元,类C是B的友元,则C不一定是A的友元,同样要看类中是否具有相应的声明;
    疑问点是否可在所有的类中定义一个万能的友元类,达到可访问所有数据的功能

抽象类(C++)

最近看了些资料,发现抽象类在C++与Java中的实现方式是不一样的,在Java中需要用到关键词abstract,而在C++中则没有要求。
含有纯虚函数的类成为抽象类,抽象类只能作为派生类的基类,不能定义对象,但可以定义指针。在派生类实现该纯虚函数后,定义抽象类对象的指针,并指向或引用子类对象,因为是用到纯虚函数实现的,所以具有以下特点:

  • 定义纯虚函数时,不能定义虚函数的实现部分
  • 在没有定义纯虚函数前,不能调用这种函数。
    抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。派生类如果不能实现所有的纯虚函数,那么派生类也成了抽象类。
    抽象类的特点:
  • 抽象类只能用作其他类的基类,不能建立抽象对象
  • 抽象类不能用作参数类型,函数返回类型或显示转换的类型
  • 可以定义指向抽象类的指针和引用,此指针可以指向他的派生类,进而实现多态性。

AB与BA概念区别

函数模板与模板函数

函数模板建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表,形式:template

模板函数将类型形参实例化的参数称为模板实参,用模板实参实例化的函数称为模板函数

函数指针与指针函数

函数指针表示指向指针的函数,如int (*pf)()
指针函数表示返回指针的函数,如int *fun()

数组指针与指针数组

数组指针 重点在指针,表示一个指针,指向一个数组,如int (*pa)[8]
指针数组重点在数组,表示包含的元素是指针,如int *ap[8]

C/C++代码的编译过程

  1. 预处理:处理各种宏定义,交给编译器
  2. 编译:编译成*.s的汇编代码
  3. 汇编:汇编代码形成*.o的机器码
  4. 链接:将目标文件(库函数)链接成完整的可执行程序

C++面向对象的三个特点并简述

  1. 封装:把数据与实现过程包装起来,隐藏实现细节,使代码模块化
  2. 继承:从一般到特殊的过程,对原有类(父类)中的功能进行扩展。主要包括:实现继承(使用基类的属性与方法,无序额外编程)、接口继承(使用基类的属性与方法的名字,子类实现)、可视继承(子类使用基类的外观和实现代码的能力)
  3. 多态:多种形态,根据对象类型的不同调用不同的函数

C/C++中强制类型转换

  1. static_cast
    • 用于基本类型间的转换
    • 不能用于基本类型指针间的转换
    • 用于有继承关系类对象间的转换和类指针间的转换
  2. dynamic_cast
    • 用于有继承关系的类指针间的转换
    • 用于有交叉关系的类指针间的转换
    • 具有类型检查的功能
    • 需要虚函数的支持
  3. reinterpret_cast
    • 用于指针间的类型转换
    • 用于整数和指针间的类型转换
  4. const_cast
    • 用于去掉const属性
    • 目标类型必须是指针或引用

指针与引用区别

(1) 引用必须被初始化,指针不必。

(2) 引用初始化以后不能被改变,指针可以改变所指的对象。

(3) 不存在指向空值的引用,但是存在指向空值的指针。

STL

  1. vector 内存管理形式
    vector分配的是连续的内存,通常 vector中分配的内存一般比实际的内存要大,即 vector.size() <= vector.capcity()
    vector在分配内存时,一般是*2分配的,这样,可以做到取放更省时间
    关键函数释义:
    resize: 修改 size ,当 resize的值大于 size/capcity 时,则 size/capcity 都变;若小于时,则只改变 size 不改变 capcity;
    reserver :修改 capcity,但不一定有效,当 vector 的现有实际 size 大于 reserve(n)n 值时,vector 不做任何修改;
    push_back:构造临时变量,尾部添加,析构临时变量
    emplace_back:直接尾部添加,效率更高,但是,代码可读性和容错性较差

Lambda 表达式

Lambda 表达式是创建匿名函数对象的一种简易途径,常用于把函数当参数传。
在这里插入图片描述
捕获列表:用来获取上下文中的数据,常见形式,[], [=], [&]
参数列表:函数的输入
函数体:函数的实现
可变规则: multableconst
异常类型:抛出异常
返回类型:可以省略,直接在 函数体return 即可
示例:

    sort(arr.begin(), arr.end(), [&tmp](int a, int b) {
        std::cout << "tmp = " << tmp << std::endl;
        return a > b;
    });

C++关键词

constexpr

constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。
修饰普通常量 简单讲就是把变量常量话,例如,

    constexpr int num = 1 + 2 + 3;
    int url[num] = {1,2,3,4,5,6};

若不加 constexpr 修饰,则会报错url[num] 定义中 num 不可用作常量

explict

指定构造函数或转换函数 (C++11起)为显式, 即它不能用于隐式转换和复制初始化,其目的是为了避免隐式转换的构造函数出现。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值