c++ plus复习(二)

系列文章目录

c++ plus复习(一)


文章目录

  • 系列文章目录
  • 其他补充
  • 模板
    • 函数模板
    • 类模板
  • 多文件程序
  • 内存管理
  • 面向对象编程oop
    • 构造与析构
    • const
    • 构造
  • 类继承


其他补充

运算符重载

// 用于 if(obj) 中的obj的判断
operator void*()
{
    return (void*)0;  // or return (void*)1;
}

类型转换重载
operator typeName(); 虽然该函数没有声明返回类型,但应返回所需类型的值。使用转换函数时要小心,可以在声明构造函数时使用关键字explicit,以防止它被用于隐式转换。

  • 转换函数必须是类方法
  • 转换函数不能指定返回类型
  • 转换函数不能有参数

函数默认值要在调用之前

变量名是内存的别名,引用是变量的别名;引用之于被引用对象犹如变量名之于内存。

// 严重错误,const引用临时变量,函数结束时该临时变量将不存在
const string& fun()
{
    string tmp;
    return tmp;
}

模板

函数模板

函数,模板具体化,模板都可以重载

模板实例化:
具体化:

void add(int a, int a){}

template<typename T>
void add(T a, T b){}

template<>
void add(int a, int b){}
template<>
void add<int>(int a, int b){}

add(1.1, 2.1);
add<float>(1.1, 2.1);

decltype

decltype(expression) var;
  1. 如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
  2. 如果expression是一个函数调用则var的类型与函数的返回类型相同。
  3. 如果expression是一个左值,则var为指向其类型的引用。一步步来,第一步都不通过才进入第三步
  4. 如果前面的条件都不满足,则var的类型与expression的类型相同。

trailing return type

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
    return x + y;
}

类模板

多文件程序

head.h

#ifndef HEAD_H_
#define HEAD_H_

int fun(int a, int b)
{ return a+b; }

#endif

test1.cpp

#include    // 从默认系统库中搜索
#include "head.h"  // 从当前目录下搜索头文件

void fun2(int, int);
int main(int argc, const char** argv)
{
    int a = 0, b = 1;
    fun(a, b);
    fun2(a, b)
}

test2.cpp

#include "head.h"
int fun2(int a, int b)
{ fun(1, 2); return a+b; }

以上在连接时将报错:
multiple definiton of ‘func(int, int)’

在两个.cpp文件中同时存在fun函数的定义

解决方法

  1. 在head.h中将函数定义改为声明,定义挪入.cpp中
  2. 将head.h中的函数定义改为模板(为什么可行?模板实例化是如何进行的?test1.cpp和test2.cpp中模板实例化生成的函数不同?还是模板只生成一个函数?)

内存管理

  • 自动存储持续性:栈,寄存器
  • 静态存储持续性:全局区、全局静态区、局部静态区
    存储在全局区,整个程序运行期间都存在。默认初始化0。c++变量遵循单定义规则。
    • int a;全局变量,可以被其它文件引用
    • static int a;只能在本文件内引用
    • {static int a;}局部静态变量,只能在局部引用
#include 
int x;                              // 0初始化
int y = 5;                          // 常量表达式初始化   
long z = 13 * 13;                   // 常量表达式初始化
const double pi = 4.0 * atan(1.0);  // 动态初始化
  • 线程存储持续性:略
  • 动态存储持续性:堆

函数
C++函数的作用域可以是整个类或整个命名空间(包括全局的),但不能是局部的(因为不能在代码块内定义函数,如果函数的作用域为局部,则只能对它自己是可见的,因此不能被其他函数调用。这样的函数将无法运行)。

外部链接性的函数:
extern return_type fun_name(parameter list); // extern是可选项

内链接性:

static int private_f(double x);

static int private_f(double x)
{
    ...
}

如果在程序的某个文件中调用一个函数。如果该函数是静态的,则在该文件中找;如果不是静态的则在全文件中找,若找到连个两个定义则报错;如果都没找到,则在库中搜索。如果定义了与库函数同名的函数,则会使用用户定义的函数。

变量
变量是如何创建的(由编译器自动创建还是手动new),变量数据的存储位置,变量的作用域,链接性

regitster
关键字register最初是由C语言引入的,它建议编译器使用CPU寄存器来存储自动变量
register int count_fast; // request for a register variable
这为了提高变量的访问速度。

如今在C++中此关键字几乎无用,保留主要是为了兼容旧代码。

volatile
告诉编译器不要对变量进行优化。

mutable
即使类变量为const,某个成员也可以被修改。

struct data{
    char name[30];
    mutable int accesses;
    ...
};
const data veep = {"abc", 3};
veep.accesses = 30;   // allowed

const
在C++中,const限定符对全局变量的默认作用范围进行了修改。
const int cina = 10;实际上是static的,即只能在本文件中使用,可 以通过包含const头文件的方式使用,因为是const的所以cosnt是否是外部或内部不影响。
可以通过extern const int states = 50;声明外部链接

extern “C”
g++编译器将以C的语法编译此代码,即fun函数不会编译成fun_i_c_而是_fun

面向对象编程oop

通常我们将类的声明放在.h文件中去,而将实现放到.cpp中去。

调用默认构造函数时不要加(),因为像函数调用。

类中定义的成员函数被所有对象所共用,通过this指针确定对象。

static成员函数没有this指针。

构造与析构

int main(int argc, const char** argv)
{
    Clas() = default;   // 纯种默认构造函数,显示的让编译器生成,否则编译器不生成
    Clas str("default construct");           // 默认构造
    Clas str2;                               // 默认构造
    Clas str3 = Clas("default construct");   // 默认构造(可能会创建临时对象,也可能不会,我这里不会)
    str2 = str3;                             // operator=
    str2 = Clas("tmp then operator=");       // 先用默认构造出一个临时对象,再operator=
                                             // 临时对象使用完后立即析构,有些编译器也可能过会在析构
    // 构造顺序 str  str2 str3,析构顺序与之相反
}

如果对象在初始化时,是通过其他对象初始化的则调用拷贝构造函数,否则调用默认构造函数
ClassType obj(…); ClassType obj = ClassType(…); // 默认构造
ClassType obj(otherobj); 等价于 ClassType obj = otherobj; // 拷贝构造

const

const Cls str1 = {};  // 列表初始化

...
const Cls& show(const Cls& sr) const;
// 括号中的const表明不修改sr对象,括号外的const表明不修改*this
// 类似const Cls& show(const Cls* this, const Cls& sr);
// 类似 str.show()  <=> show(&str)

const CLASS a = {};
a.show(); // show必须是 return_type show()const;

c++ plus复习(二)_第1张图片

声明类指是描述了对象的形式,并没有创建对象,在创建对象前,将没有用于存储值的空间。

class Bakery
{
private:
    const int Months = 12;  // 行不通
    double costs[Months];
    ...
};

// 两种方法解决
// 1.
// 用这种方式声明枚举并不会创建类数据成员,所有对象中都不包含枚举。Months只是
// 一个符号名称,在作用域为整个类的代码中遇到它时,编译器将用30来替换它
class Bakery
{
private:
    enum {Months = 12};
    double costs[Months];
    ...
};
// 2.
// 创建static常量,存储在静态区,可被所有Bakery对象共享
class Bakery
{
private:
    static const int Months = 12;
    double costs[Months];
    ...
};

类内枚举

// 传统枚举如果枚举量的名称相同将发生冲突
// 类内枚举
enum egg_old {SMALL, MEDIUM, LARGE};        // unscoped
enum class t_shirt {SMALL, MEDIUM, LARGE};  // scoped
egg_old one = SMALL;                        // unscoped
t_shirt rolf = t_shirt::MEDIUM;             // scoped
int king = one;   // implicit type conversion for unscoped
int ring = rolf;  // × not allowed, no implicit type conversion
if (king < LARGE)  // allowed
    std::cout << "LARGE converted to int before comparison.\n";
if (king < t_shirt::MEDIUM)  // × not allowed
    std::cout << "Not allowed:< not defined for scoped enum.\n";
// 显示类型转换
int Frodo = int(t_shirt::SMALL);  // Frodo set to 0
// 显示指定枚举类的底层依赖
enum class : short pizza {SMALL, MEDIUM, LARGE};

static

int ClassName::static_number = 0;
这条语句将静态成员static_number的值初始化为0 。不能在类声明中初始化静态成员变量,这是因为声明表述了如何分配内存,但并不分配内存。可以使用这种格式ClassName obj;来创建对象,从而分配和初始化内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。初始化语句指出了类型,并使用了作用域运算符,但没有使用关键字static 。

初始化是在方法文件中,而不是在类声明文件中进行的,因为类声明位于头文件中,程序可能将头文件包括在其他几个文件中。如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误。

delete 定位new
delete空指针是合法的,没有副作用。

delete可与常规new运算符配合使用,但不能与定位new运算符配合使用。delete/[]知道从new/[]中分配了多少内存。

// 先通过new申请块内存
char* pch = new char[512];
// 通过定位new分配
Clas* pc1 = new(pch)Clas();
// 再分配
Clas* pc2 = new(pch+sizeof(Clas)) Clas();
// 显示调用析构函数
pc2->~Clas();
pc1->~Clas();
// 释放通过new[]分配的内存
delete[] pch;

嵌套结构和类
c++ plus复习(二)_第2张图片
绝大部分情况下,private静态/非静态成员数据/函数只能内部访问

构造

构造函数():初始化列表  
{ 函数体,对象在执行这里的代码之前被创建 }
只有构造函数可以使用这种初始化列表语法
例如,对于const类成员,必须使用这种语法。对于被声明为引用的类成员,也必须使用这种语法。

TODO:拷贝构造做了什么

编译器默认生成的:
默认、拷贝、赋值、析构
如果将一个类对象复制或赋值给另一个类对象,逐成员赋值将使用成员类定义的复制构造函数和赋值运算符。

类内初始化
c++ plus复习(二)_第3张图片

类继承

public继承建立 is-a 关系
水果是基类,香蕉是派生类

编译器生成的代码将在程序执行时,根据对象类型将虚函数名关联到 基类::fun或 派生类::fun中,总之编译器对虚方法使用动态联编。

空类对象的大小为1字节,含有虚函数的空类大小为虚表指针大小(32 bits/ 64 bits)

子类方法会将同名的父类方法隐藏

class Base
{
private:
    /* data */
public:
    virtual void base_fun1()
    {
        std::cout << "base fun1" << std::endl;
    }
};
class Derive:public Base
{
private:
    /* data */
public:
    ;
    void base_fun1(int a){
        ;
    }
};
int main()
{
    map<int, int> mp = {{1,2},{3,5}};
    Derive d;
    d.Base::base_fun1();
}

抽象基类与protected


class BaseEllipse
{
protected:
    double x_;
    double y_;
public:
    virtual void move(double nx, double ny) = 0;
    virtual double area() const = 0;
    BaseEllipse(double nx = 0, double ny=0):x_(nx), y_(ny) {}
    virtual ~BaseEllipse() {}
};
class Ellipse : public BaseEllipse
{
protected:
    double a_;
    double b_;
    double angle_;
public:
    Ellipse(double nx=0, double ny=0, double a=0, double b=0, double angle=0): 
        BaseEllipse(nx,ny), a_(a), b_(b), angle_(angle) {}
    virtual ~Ellipse() {}
    virtual void move(double nx, double ny)
    {
        x_ = nx;
        y_ = ny;
    }
    virtual double area() const
    {
        return 3.14159 * a_ * b_;
    }
    void rotate(double nang)
    {
        angle_ += nang;
    }
    void scale(double sx, double sy)
    {
        a_ *= sx;
        b_ *= sy;
    }
};
class Circle : public BaseEllipse
{
protected:
    double r_;
public:
    Circle(double x=0, double y=0, double r=1):
        BaseEllipse(x, y), r_(r) {}
    virtual ~Circle() {}
    virtual void move(double nx, double ny)
    {
        x_ = nx;
        y_ = ny;
    }
    virtual double area() const
    {
        return 3.14159 * r_ * r_;
    }
    void scale(double ss)
    {
        r_ *= ss;
    }
};
void mpf(BaseEllipse* p)
{
    std::cout << p->area();
}
int main()
{
    Circle c;
    mpf(&c);
}

子父类的构造、析构、拷贝构造与赋值运算符

  1. 子类没有new对象
    在创建子类时,会自动调用父类组件的四类函数。构造时自动调用,析构时同样如是。所以只需要管理好子类中的对象即可,继承的组件会自动调用对应的默认函数
  2. 子类有new对象
    析构: 析构函数会自动调用基类析构;(析构函数不重载)
    构造: 若子类构造函数(包括重载形式)没有在初始化列表中显示写出父类的构造,则自动通过父类的默认构造函数(非重载形式)初始化父类部分的组件。
    拷贝构造: 同构造,建议在初始化列表中显示调用父类的copy constructor
    赋值运算符: 显示调用父类的 operator=,如果不显示调用,则父类组件没有赋值

c++ plus复习(二)_第4张图片
如果子类中没有new的成员,则不需要特别定义四类函数。

你可能感兴趣的:(C++,c++)