复试篇---C/C++

一:内联函数与宏定义的

宏定义:

#define 标识符 字符串

eg:

#define max(a,b) a>b ? a:b;

用法:在主程序中遇到max(a,b)时候会把a和b替换到后面的式子

优点:(1)省去函数调用等步骤,提高效率。函数调用要开辟一个栈空间,将返回地址、形参等压栈,函数返回的时候还要释放栈空间。这会带来很多的时间开销。(2)宏定义与参数无关,较灵活。而函数调用会规定形参的类型,只能传进特定实参。

缺点:宏展开会造成代码长度过长。带参数宏定义的时候,很多时候会引起歧义,eg cal(a,b) a*b,若函数中是max(a++,b),替换的时候会变成a++ > b ? a++:b,a自增了两次,所以带参数宏定义的时候比较讲究。

补充:程序编译的时候会经过几个步骤(1)预处理,做程序展开的和替换的操作,例如将.h文件展开到#include处,进行宏替换等工作,不做任何运算功能 (2)编译:将高级语言编译成汇编语言 ,将程序形成若干个目标模块。(3)链接。将目标模块和他们需要的库函数链接在一起,形成一个完整的装入模块。(4)装入。将最终模块装入内存中。

 

内联函数:

用法:

inline

eg inline int max(int a,int b){

    a>b ? a:b

}

优点:

在调用内联函数的时候,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数 都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检 查,或者进行自动类型转换。

缺点:

内 联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,若每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。(若两次调用同一个函数,且函数的程序较长,那么多次用内联就会多次复制,不如只进行函数调用好

 

二:C++三大特性:封装、继承,多态

(1)public 、protected、private三者区别:

若是public,则子类和类外的任意访问者都可以直接访问,代表数据是公开的,不加以保护的。

若是protected,则只有自己和子类才可以直接访问,对数据稍微加以保护

若是private:则只有自己才可以直接访问。

 

三:函数重载

定义:在同一个作用域内的几个函数名字相同,但是形参不同(形参的数量或者类型不同)。编译器会根据传递的形参类型推断想要的是哪个函数

作用:在一定程度上减轻程序员起名字、记名字的负担

注:和重写区别开来,重写是指父类中的某个函数前面加了virtual,变成虚函数。然后子类定义了一个函数名、类型、返回值都完全一致的函数。这就叫做重写。

四:类和对象

(1)在.h文件中定义类的方法

为了避免在main函数中定义多个类造成main函数可读性不强,可将类的定义放在.h头文件中,头文件名字要和类名一致。

eg:"sales_data.h"头文件中这样定义一个类

#ifndef SALES_DATA_H
#define SALES_DATA_H

class sales_data{
    ...
}

#endif

上面定义一共分为三部分:

①#ifndef  与#endif ,作用:主要是为了防止头文件的嵌套,避免多次重复预处理和编译。

②#define

③类的定义部分

(2)构造函数:

类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数,无论何时只要类的对象被创建,就会执行构造函数。

构造函数的函数名和类名一样,且没有返回值。

默认构造函数:若类中没有显式地给出构造函数,则编译器会隐式地提供一个构造函数,叫做默认构造函数。该默认构造函数不接受任何实参

eg:

struct Sales_data{
    Sales_data() = default; //默认构造函数,没有形参, = default可写可不写,写的话突出是构造函数
    Sales_data(const string &s,unsigned n,double p) : bookNo(s),units_sold(n),revenue(p*n)            
    { };
    Sales_data(std::istream &);
}

Sales_data :: Sales_data(std::istream &is){
    read(is,*this); //从is中读取一条信息存到this对象中
}

第二个构造函数中:和{}中间的部分叫做构造函数初始值列表,函数体是为空的,即只在初始值列表中初始化,函数不需要其他的操作,当然,也可以不需要初始值列表,而是在构造函数体内干活,如第三个构造函数。

 

(3)拷贝、析构

对象的拷贝相当于把数据成员复制过去

 

五:访问控制和封装

(1)使用struct和class定义类有什么不同:使用struct定义默认在访问说明符之前的成员都是public,而使用class则是private,仅仅是这一点不同,其他定义方式都是一样的。

(2)对象和类

 

面向对象和面向过程的区别:形象理解:蛋炒饭和盖浇饭

(1)面向对象是以功能来划分问题,而不是步骤。面向对象就是高度实物抽象化、面向过程就是自顶向下的编程!

(2)面向对象把数据和函数模块整合在一起,面向过程是分开。

具体可以参照这篇博文, https://blog.csdn.net/jerry11112/article/details/79027834

(3)怎么样理解封装性:

封装的目标是实现程序的高内聚,低耦合,把程序给模块化。而类的出现实现了这个功能。而且。类中还可以用访问控制符,使类中的属性只要调用类中的方法才可以使用,大大加强了类的封装性。

作用:①:被封装的类的具体实现细节可以改变,但是并不需要调整用户级别的代码。一旦数据成员被定义成private,类的作者就可以比较自由地去修改数据。换句话说,只要类的接口不变,用户代码就不会改变。使得代码更加容易维护(软件工程中的概念)②:把数据成员定义成private的,还可以避免用户对数据造成破坏。一旦数据出现问题,还方便我们去定位问题所在

 

(4)友元:如果某个类的数据被定义成private了,则不是类的成员函数,访问不了这个私有数据。要实现非成员函数也可以进行这个数据的访问,可以把非成员函数定义为这个类的友元。(注意是在原本那个类里面定义)

class Sales_data{
    friend Sales_data add(const Sales_data&,const Sales_data&);
    private:
        double revenue = 0.0;
}

这里也要区分一下成员函数和接口函数,若一个函数是在类外定义的,则这个函数是接口函数,但是非成员函数。

类还能把其它类定义成友元,也可以把其他类的成员函数定义成友元

class Screen{
    friend class Window_mgr; 
}

则Window_mgr类可以访问类Screen的所有成员

class Screen{
    friend void Window_mgr :: clear(ScreenIndex);
}

上面定义友元函数的时候并不是对这个函数的真正声明,下面要用的时候,必须对这个函数再次声明。也就是说友元声明的作用只是影响访问权限,并非普通意义上的定义。

 

六:泛型算法---在#include<algorithm>头文件中

定义:标准库并未给每个容易都定义成员函数来实现这些操作,而是定义了一组泛型算法。这组泛型算法实现了一些经典算法的公共接口,可以用于基于不同类型的元素和多种容器类型,是基于迭代器的

(1)只读算法:

find(v.begin(),v.end(),val);  ---在v中查找val,并返回val的位置

equal(v1.cbegin(),v1.cend(),v2.cbegin());--比较v1中的每个元素,在v2中是否都有可以对应相等的元素,若有,则返回true

 

(2)写算法:

fill(v.begin(),v.end(),0);  ----重写v中元素为0

注意一下一个错误:

vector<int>vec;
fill_n(vec.begin,10,0);

原本想实现把vec的前十个元素重置为0,但是这里vec是空的,并没有十个元素,引发错误

 

replace(v.begin(),v.end(),0,42);---将v中为0的元素都替换成42

 

(3)重排算法

//排序且删除words里面的重复元素
vector<string>words;

sort(words.begin(),words.end());

//unique会返回去重后的序列的后面的指针
auto end_unique = unique(words.begin(),words.end());
words.erase(end_unique,words.end());   

泛型算法只能对迭代器操作,不能对容器本身操作,所以要真正删除容器的元素时,应调用容器本身的函数

 

(4)lambda:可作为一些函数的参数,实现一些具体的功能

定义:

[] () ->returntype { };

[]里面是这个函数需要捕获局部变量、(这个局部变量在之前有定义)()是这个函数的形参,接下来是返回类型,函数体部分。

 

捕获与返回:

①值捕获:捕获一个值的拷贝

size_t v1 = 42;
auto f = [v1]{return v1};

v1 = 0;
auto j = f();

这里属于值捕获,得到的j还是42

 

②:引用捕获

size_t v1 = 42;
auto f = [&v1]{return v1;};
v1 = 0;
auto j = f();

得到的j还是0

 

七:继承与派生

(1)动态绑定:当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定

eg:

class Quote{
    public:
        virtual double net_price(size_t n) const;
}


double print_double(const Quote &item,size_t n){
    double ret = item.net.price(n);
    return ret;
}

print_totle(basic,20);
print_totle(bulk,20);

basic 是基类对象,执行的是基类的函数,bulk是派生类对象,执行的是派生类的对象

(2)定义一个派生类:

class Bulk_quote : public Quote{
  ...  
};

冒号后面加上访问说明符

(3)首先初始化基类的成员部分,然后按照声明的顺序依次初始化派生类的对象

(4)防止继承的发生

class Noderived final{ ... };  //Noderived 不能作为基类
class last final : Base{ ... };  //last是final继承,后面不能作为基类

(5)类型转换

派生类可以利用指针或者引用向基类转换,而基类不能像派生类转换

我们可以将一个派生类对象拷贝、移动或者赋值给一个基类对象,不过这种操作只处理派生类对象的基类部分

 

八:抽象基类

含有纯虚函数的类是抽象基类,抽象基类负责定义接口,而后续的其他类可以覆盖该接口

纯虚函数定义:double net_size(size_t)  = 0;

不能创建抽象基类的对象

 

九:虚析构函数

当我们delete一个动态分配的对象的指针时,将执行析构函数。如果我们想将析构函数动态绑定,则需要在基类处设置一个虚析构函数(和前面的虚函数理解是一样的)

eg:

class Quote{
    public:
        virtual ~Quote() = default;  //动态绑定析构函数 
}

Quote * it = new Quote;
delete it;  //调用Quote的析构函数

it = new Bulk_quote;
delete it;   //调用Bulk_quote的析构函数

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值