C++ primer类的基础精讲

目录
  • 定义抽象数据类型
    • 初探this和
    • 构造函数
  • 访问控制和封装
    • 友元
  • 类的其他特性
    • 可变数据成员
    • 返回*this的成员函数
    • 友元类
  • 构造函数再探
    • 构造函数初始值列表
    • 默认构造函数的作用
    • 聚合类
  • 类的静态成员

定义抽象数据类型

初探this和

struct Sales_data
{
    string isbn(){return bookNo;}
    Sales_data & combine(const Sales_data&);
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0;
};
Sales_data total;

引入this

对于isbn成员函数的调用: total.isbn();

当我们调用成员函数时,实则上是在替某个对象调用它。在上面的调用中,当isbn返回bookNo时,实际上隐式地返回total.bookNo.

成员函数通过一个名为this的额外隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。例如,如果调用total.isbn(),编译器负责把total的地址传递给isbn的隐式形参this,可以等价认为编译器将该调用重写成Sales_data::isbn(&total),调用Sales_data时的isbn成员时传入了total的地址。

在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看做this的隐式调用,例如,isbn在使用bookNo时,隐式地使用this指向的成员,就如同this->bookNo一样。

构造函数

定义:类通过一个或几个特殊的成员函数来控制其对象的初始化,这些函数叫做构造函数。

无论何时,只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名一样,构造函数没有返回类型,一个类可以拥有多个构造函数,但每个构造函数之间必须在参数数量或参数类型上存在不同。且构造函数不能被声明成const。

当一个类没有定义任何构造函数时,编译器会给类自动添加一个默认构造函数,该构造函数无须任何实参对对象进行初始化。

对前面的Sales_data类进行编写构造函数

struct Sales_data
{
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

(1)=default的含义

如果需要默认构造函数起作用,那么可以在参数列表后面写上=default来要求编译器生成默认构造函数。

(2)构造函数初始值列表

Sales_data(const string &s):bookNo(s)()
Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}

上面出现了两个新的构造函数的写法,该部分称为构造函数初始值列表。

负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的成员初始值。

当某个数据成员被构造函数初始值列表忽略时,他将以合成默认构造函数相同的方式隐式初始化。所以,第一个构造函数等价于:

Sales_data(const string &s):bookNo(s),units_sold(0),revenue(0)();

访问控制和封装

访问控制符public和private

定义在public说明符之后的成员在整个程序可被访问。

定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。

Sales_data类的新形式

class Sales_data
{
public:
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

友元

类允许其他类或者函数访问他的非公有成员,方法是令其他类或者函数成为他的友元。如果一个类想把一个函数作为他的友元,只需要增加一个friend关键字开始的函数声明语句即可

class Sales_data
{
    friend Sales_data add(const Sales_data &,const Sales_data&);
    friend istream &read(istream&,Sales_data&);
    friend ostream &print(ostream&,const Sales_data&)
public:
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

友元的声明只能出现在类定义的内部。友元不是类的成员,也不受他所在区域访问控制级别的约束。

类的其他特性

可变数据成员

在一个const成员函数中,若希望修改类的某个数据成员,可以通过在变量的声明中加入mutable关键字实现

class screen{
public:
        void some_menmber() const;
private:
        mutable size_t access_ctr
};
void screen::some_member() const
{
    ++access_ctr
}

返回*this的成员函数

inline Screen &Screen::set(char c)
{
    contents[cursor]=c;
    return *this;
}
inline Screen &Screen::set(pos r,pos col,char ch)
{
    contents[r*width+col]=ch;
    return *this;
}
inline Screen &Screen::move(pos r,pos c)
{
    pos row=r*width;
    cursor=row+c;
    return *this;
}

move和set一样,返回的值是对对象的引用。

myScreen.move(4,0).set('#');

等同于

myScreen.move(4.0);

myScreen.set('#');

假如我们定义的返回类型不是引用,则move的返回值将是*this的副本,因此调用set只能改变临时副本,不能改变myScreen的值

友元类

例如,window_mgr类的某些成员需要访问screen类的内部数据,例如window_mgr的clear函数将一个指定的screen类的内容设置为空白。

class Screen{
//window_mgr的成员可以访问Screen类的私有部分
friend class Window_mgr
//Screen类剩余部分
}
class Window_mgr{
public:
    using ScreenIndex=std::vector<Screen>::size_type
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{Screen(24,80,' ')}
};
void Window_mgr::clear(ScreenIndex)
{
    Screen &s=screen[i];
    s.contens=string(s.height*swidth,' ')
}

构造函数再探

构造函数初始值列表

(1)构造函数的初始值有时必不可少

如果成员是const或者是引用的话,必须将其初始化

class ConstRef
{
public:
    ConstRef(int ii);
private:
    int i;
    const int ci;
    int &ri;
};

成员ci和ri必须被初始化,如果没有为他们提供构造函数初始值的话将引发错误。正确形式应该是: ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(i){};

如果成员是const,引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。

(2)成员初始化顺序

成员初始化的顺序与他们在类定义中的出现顺序一致。一般来说初始化的顺序没什么特别要求,不过如果一个成员是用另一个成员来初始化的,那么着两个成员的初始化顺序就关键了。

例如

class X
{
    int i;
    int j;
public:
    X(int val):j(val),i(j){}
};

而编译器实际上是先初始化i,在初始化j,而初始化i的时候发现j没有值,所以上述构造函数会发生错误。

默认构造函数的作用

当对象被默认初始化或值初始化时自动执行默认构造函数。

默认初始化在以下情况下发生:

(1)当我们在块作用域内不适用任何初始值定义一个非静态变量或数组时。

(2)当一个类本身含有类类型的成员且使用合成的默认构造函数时

(3)当类类型的成员没有在构造函数初始值列表中显示地初始化时

值初始化在以下情况发生:

(1)在数组初始化的过程中如果我们提供的初始值数量少于数组的大小时

(2)当我们不适用初始值定义一个局部静态变量

使用默认构造函数

下面的obj的声明可以正常通过

Sales_data obj();//正确,定义了一个函数而非对象
if(obj.isbn()==primer_5th_ed.isbn())//错误:obj是一个函数

但当我们试图使用obj时,编译器将报错,提示我们不能使对函数使用成员访问运算符。因为obj的实际含义是一个不接受任何参数的函数并且其返回值是Sales_data类型的对象。

如果想定义一个使用默认构造函数进行初始化的对象,正确方法是去掉对象名之后的空括号对。

Sales_data obj;

聚合类

满足一下条件的类是聚合类:

(1)所有成员都是public的

(2)没有定义任何构造函数

(3)没有类内初始值

(4)没有基类,也没有虚函数

例:

struct Data
{
    int val;
    string s;
};

类的静态成员

class Account
{
public:
    void calculate(){amount+=amount*interestRate;}
    static double rate(){return interestRate;}
    static void rate(double);
private:
    string owner;
    double amount;
    static double interestRate;
    static double initRate();
}

类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据,因此,每个Account对象将包含两个数据成员:owner和amout。只存在一个interestRate对象而且他被所有account对象共享。

静态成员函数也不与任何对象绑定在一个,他们不包含this指针。作为结果,静态成员函数不能声明成const的,而且我们也不能在static函数体内使用this指针。

到此这篇关于C++ primer类的基础精讲的文章就介绍到这了,更多相关C++类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • c++primer:变量和基本类型详解

    目录 前言 类型转换 变量声明与定义的关系 变量命名规范 复合类型 引用 指针 const限定符 const的引用 指针和const constexpr和常量表达式 auto类型说明符 decltype 头文件保护符 习题 练习 总结 前言 我只写我觉得重要的,同时把一些我觉得比较重要的习题做一下 类型转换 类型所能代表的范围决定了转换的过程 比如:当我们把一个非布尔类型的算数值赋给布尔类型时,初始值为0则代表结果为false,否则结果为true 当把浮点数赋给整数时,会舍去小数部分. 当赋给无

  • c++primer类详解

    目录 1. 定义抽象数据类型 1.1 设计Sales_data类 1.2 定义类相关的非成员函数 1.3构造函数 1.4 拷贝.赋值和析构 2 访问控制和封装 2.1 友元 2.2 类的其他特性 2.2.1 类成员再探 2.2.2 返回*this的成员函数 2.2.3 类类型 2.2.4 友元再探 2.4 类的作用域 2.4.1 名字查找和类的作用域 2.5 构造函数再探 2.5.1 2.5.2 委托构造函数 2.5.3 默认构造函数的作用 2.5.4 隐式的类类型转换 2.5.5 聚合类 2.

  • C++ Primer的变量和基本类型详解

    目录 1.类型转换 含有无符号类型的表达式 2.字面值常量 整形和浮点型字面值 字符和字符串字面值 转移序列 指定字面值的类型 布尔字面值和指针字面值 总结 1.类型转换 对象的类型定义了对象能包含的数据和能参与的运算,其中一种运算被大多数类型支持,就是将对象从一种给定的类型转换为另一种相关类型.比如 把非布尔类型的数据赋值给布尔类型时,初始值为0则结果为false,否则为true: 将布尔值赋值给非布尔类型时,初始值为false则结果为0,初始值为true则结果为1: 将浮点数赋给整数类型时,

  • 《C++ Primer》隐式类类型转换学习整理

    C++ Primer中有这样一句话:可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个转换.这句话用下面一段代码表示为: class A { A(B b);//单个实参的构造函数 func(A a); } ..... A a: B b; a.func(b);//func函数本应该接受A类型的实参,但是由于特殊构造函数的存在,所以B类型的参数b借助这个特殊的构造函数转化为A类型对象,完成转换.所以这条语句是正确的 从上述代码段可以看出来,单个实参调用的构造函数定义了类类型到其他类型的转

  • C++ Primer Plus 第四章之C++ Primer Plus复合类型学习笔记

    目录 1. 数组概述 1.1 数组的定义 1.2 数组的声明 1.3 复合类型的数组 1.4 数组的初始化规则 1.5 C++11数组初始化方法 2. 字符串 2.1 C++处理字符串的两种方式: 2.2 字符串常量的拼接 2.4 读取一行字符串的输入 3. string类 3.1 string对象的方式 3.2 复制.拼接和附加 4. 结构简介 4.1 创建结构的步骤: 4.2 结构的定义: 4.3 结构的初始化(C++11) 4.4 成员赋值 5. 共用体 5.1 结构体和共用体的区别 5.

  • C++ primer类的基础精讲

    目录 定义抽象数据类型 初探this和 构造函数 访问控制和封装 友元 类的其他特性 可变数据成员 返回*this的成员函数 友元类 构造函数再探 构造函数初始值列表 默认构造函数的作用 聚合类 类的静态成员 定义抽象数据类型 初探this和 struct Sales_data { string isbn(){return bookNo;} Sales_data & combine(const Sales_data&); double avg_price() const; string b

  • Java基础精讲方法的使用

    目录 一.方法 二.方法的重载 三.方法递归 四.小结 一.方法 方法是什么? 在Java中方法就相当于C语言中的函数.因为有时候我们需要一块代码重复使用,这时候就可以使用方法. 为什么要用方法? 1. 是能够模块化的组织代码 ( 当代码规模比较复杂的时候 ). 2. 做到代码被重复使用 , 一份代码可以在多个位置使用 . 3. 让代码更好理解更简单 . 4. 直接调用现有方法开发 , 不必重复造轮子 方法的基本语法格式: public static 返回类型 方法名称(形参列表){} 在这里要

  • SpringBoot2零基础到精通之数据库专项精讲

    目录 1 数据库连接 1.1 配置数据库连接信息 1.2 整合Druid数据源 2 SpringBoot整合MyBatis 2.1 配置文件开发 2.2 纯注解开发 3 SpringBoot整合MyBatis-Plus 3.1 普通的CRUD方法 3.2 MyBatis-plus的分页实现 1 数据库连接 1.1 配置数据库连接信息   如果想要使用数据库连接池连接数据库进行SQL操作的话,在SpringBoot中需要经过如下三个步骤: 第一步: 导入jdbc开发的启动场景 <dependenc

  • RestTemplate在Spring或非Spring环境下使用精讲

    目录 一.什么是RestTemplate? 二.非Spring环境下使用RestTemplate 三.Spring环境下使用RestTemplate 一.什么是 RestTemplate? RestTemplate是执行HTTP请求的同步阻塞式的客户端,它在HTTP客户端库(例如JDK HttpURLConnection,Apache HttpComponents,okHttp等)基础封装了更加简单易用的模板方法API.也就是说RestTemplate是一个封装,底层的实现还是java应用开发中

  • RestTemplate的DELETE及PUT等请求方法使用精讲

    目录 一.RESTful风格与HTTPmethod 二.使用DELETE方法去删除资源 三.使用PUT方法去修改资源 三.通用请求方法exchange方法 四.使用HEAD方法获取HTTP请求头数据 五.使用OPTIONS获取HTTP资源支持的method 本文是精讲RestTemplate第5篇,前篇的blog访问地址如下: RestTemplate在Spring或非Spring环境下使用精讲 RestTemplate实现多种底层HTTP客户端类库的切换用法 RestTemplate发送HTT

  • C++模板的特化超详细精讲

    目录 一.泛型编程 二.函数模板 2.1.函数模板的概念 2.2.函数模板的格式 2.3.函数模板的原理 2.4.函数模板的实例化 2.4.1.隐式实例化 2.4.2.显示实例化 三.类模板 3.1.类模板的定义格式 3.1.类模板的实例化 四.模板的特化 4.1.概念 4.2.函数模板特化步骤 4.3.类模板的特化 4.3.1.全特化 4.3.2.偏特化 一.泛型编程 我们前面已经学过函数的重载,实现了在函数名相同的情况下,实现不同的功能! 例如: void Swap(int& left, i

  • Jetpack Compose状态专篇精讲

    目录 1.remember 2.rememberSaveable 3.状态提升 4.状态管理 将Composable作为可信来源 将状态容器作为可信来源 将 ViewModel 作为可信来源 应用中的状态是指可以随时间变化的任何值.这是一个非常宽泛的定义,从 Room 数据库到类的变量,全部涵盖在内. 由于Compose是声明式UI,会根据状态变化来更新UI,因此状态的处理至关重要.这里的状态你可以简单理解为页面上展示的数据,那么状态管理就是处理数据的读写. 1.remember remembe

  • Mysql数据库的主从复制与读写分离精讲教程

    目录 前言 一.MySQL主从复制 1.支持的复制类型 2.主从复制的工作过程是基于日志 3.请求方式 4.主从复制的原理 5.MySQL集群和主从复制分别适合在什么场景下使用 6.为什么使用主从复制.读写分离 7.用途及条件 8.mysql主从复制存在的问题 9.MySQL主从复制延迟 二.主从复制的形式 三.读写分离 1.原理 2.为什么要读写分离呢? 3.什么时候要读写分离? 5.目前较为常见的MySQL读写分离 四.案例实施 1.案例环境 2.实验思路(解决需求) 3.准备 4.搭建My

  • SpringBoot拦截器使用精讲

    目录 定义拦截器 注册拦截器 指定拦截规则 实现登陆功能 验证登陆及登陆拦截功能 我们对拦截器并不陌生,无论是 Struts 2 还是 Spring MVC 中都提供了拦截器功能,它可以根据 URL 对请求进行拦截,主要应用于登陆校验.权限验证.乱码解决.性能监控和异常处理等功能上.Spring Boot 同样提供了拦截器功能.  在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步: 定义拦截器 注册拦截器 指定拦截规则(如果是拦截所有,静态资源也会被拦截) 定义拦截器 在

  • Java 数据结构与算法系列精讲之单向链表

    目录 概述 链表 单向链表 单向链表实现 Node类 add方法 remove方法 get方法 set方法 contain方法 main 完整代码 概述 从今天开始, 小白我将带大家开启 Jave 数据结构 & 算法的新篇章. 链表 链表 (Linked List) 是一种递归的动态数据结构. 链表以线性表的形式, 在每一个节点存放下一个节点的指针. 链表解决了数组需要先知道数据大小的缺点, 增加了节点的指针域, 空间开销较大. 链表包括三类: 单向链表 双向链表 循环链表 单向链表 单向链表

随机推荐