(Boolan) C++面向对象高级编程(四)

2024-04-18 08:08

本文主要是介绍(Boolan) C++面向对象高级编程(四),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

之前几次,对C++中的一些核心话题进行了一些梳理,主要都是集中在关于面向对象的思想方面。我通过部分故事的思路,结合生活来理解了关于面向对象的一些问题。如果需要可以回看我文章列表的中的文章。今天的部分比较零散,我的思考可能也有限,很多部分我的思考也不够,不足以用故事来涵盖,但是既然是个故事狗,那么还是来说一下,类型到底是干什么用的。
但是C++的内容非常庞大,之前也仅仅是冰山的一角,还有非常丰富的知识有待梳理。
在梳理今天内容的之前,我想先简单说明一下关于类型的概念,为什么计算机系统中需要设计变量类型呢?如果你熟悉JavaScript,那么在其中声明变量直接使用var xxx就行了,而在Python中,甚至连具体的类型名称都不用写!岂不是快哉?!省下了大量的力气来讨论变量类型,如果在c++11 之前,auto关键字还不能用于自动声明变量类型的时候,C++对于变量类型的要求可谓是相当的严格了。
那么咱们就先来说说,为什么需要有变量类型这件事吧。

  • 变量类型的作用
    这个需要从非常原始的话题开始说起了~~~~原始到要存二进制开始说起。。。
    其实我们都知道,计算机,之所以叫做计算机,是因为他只能通过计算数字(真的就是计算数字,连数学都算不上。。。),而且为了方便计算机系统设计和实现,采用的是我们基本上很难读懂的二进制来计算我们想要的结果的。二进制很方便的表明了的状态。但是,对于二进制来说,我们基本没办法直接读懂到底其中在说啥(就算能读懂,也记不住,可以参考一个智力比赛记灯泡的开关(个人觉得这个比赛丧心病狂,在此不吐槽了)。。。。)。他的最根本的优点也十分突出, 那就是简单(简单到只有两个数) 但随之而来的缺点也很明显,那就是他实在太长了。比如一个很简单的236使用二进制表示却成了1110 1100(幸好我还很用心的添加了空格<实际怕自己写错被打脸>),而这些我们很难直接读懂的东西会连续的写在一起,像‭0011 1001 0110 1100 1010‬这样,那么我们就很那区分出来,这到底是我们日常中熟悉的235210还是57 和 869等等等等。因为这牵扯到我上篇文章讲的那个钥匙和箱子的故事,也就是我们并不能通过数字来判断,多少个单位能够算作一组,这一组能够表达一个想要的数字。那这个时候,再拿出之前的故事,想知道箱子里面放的啥,那在箱子上贴上名字就行啦。其实类型相当于这个标签,在连续的二进制数字的序列中,就可以轻松知道,这个变量占有多大的内存。
    因此,类型声明,其实是为了告诉编一起,我这个变量需要多大的内存空间,比如int一般为4个字节,编译器可以非常方便的按照大小取出全部的数据来了。那么为什么C/C++对于内存这么关注,而Python这类语言不关注呢?其实,对于python这类解释形语言来说,在他和内存之间其实还有一个东西叫做虚拟机,他主要负责控制变量的类型计算,而不需要Python亲力亲为来控制变量的类型了。(其实也就是把类型控制的部分抽出来了,不代表他没有类型,不然Python中不会有type()内置函数了)
    那么C/C++的类型如此重要,会不会牵扯到一个问题呢,那就是类型转换的问题了。

    转换函数

  • 转为其它类型(Conversion function)

    //定义
    class Fraction
    {
    public:Fraction(int num, int den = 1) :m_numerator(num), m_denminator(den){}//定义转换函数//可以告诉编译器,可以将Fraction转换为doubleoperator deouble() const { if(m_denominator)return (double) (m_numerator / m_denominator) ; elsereturn 1.0;}
    private:int m_numerator;int m_denominator;
    };
    //使用
    Fraction f(3, 5);
    double d = 4 + f;  //调用operator double() 将f转为0.6,再进行运算
    //编译器会先查看是否存在`double operator+(int, Fraction)`的函数声明,如果该函数存在,则使用操作符重载
    //如果操作符重载不存在,编译器会查找是否存在Fraction想double的转换函数
    • 语法:

      operator targetType() const
      { return statement; 
      }
    • 转换函数的功能
      • 可以通过该函数告诉编译器,可以将自己转换为某一个类型
    • 转换函数的注意事项
      • 不需要参数
      • 不需要返回类型
      • 返回值需要考虑是否为const的问题
  • 其它类型转为自己的类型(non-explicit-one-argument constructor)
    //定义
    class Fraction
    {
    public://non-explicit-one-argument constructor//构造函数中部分有默认值,构造时,不需要传递全部参数,只要一个实参就可以Fraction(int num, int den = 1): m_numerator(num), m_denominator(den) {}Fraction operator+(const Fraction& f){return Fraction(....);}
    private:int m_numerator;int m_denominator;
    };
    //调用
    Fraction f(3, 5);
    Fraction d2 = f + 4;//调用non-explicit ctor 将4转为Fraction,再调用operator+(该函数按照上方定义得知,需要Fraction+Fraction)
  • 语法

    • 按照正常构造方法,但在形参列表处,需要对不需要的参数写入默认值,最后只留下一个形参需要外部传入即可构造对象,该构造函数,可以将其他类型转化为需要的类型
  • explicit关键字

    • 提出问题
      //定义
      class Fraction
      {
      public://non-explicit-one-argument constructor//构造函数中部分有默认值,构造时,不需要传递全部参数,只要一个实参就可以Fraction(int num, int den = 1): m_numerator(num), m_denominator(den) {}Fraction operator+(const Fraction& f){return Fraction(....);}operator double() const {if(m_denominator)return (double) (m_numerator / m_denominator) ; elsereturn 1.0;}
      private:int m_numerator;int m_denominator;
      };
      //调用
      Fraction f(3, 5);
      Fraction d2 = f + 4; // [ERROR]Ambiguous
      //符合语法,但是此处存在二义性,运行时异常
      //语义一:转换函数,编译器可以将f转换为double,再执行加法运算
      //语义二:存在non-explicit-one-argument constructor,所以编译器也可以将常数4构造为Fraction,再进行加法运算。
      //由于以上两种方案,都可以实现该条语句的调用,所以编译器产生二义性,不能选择,随报错。
    • explicit的用法

      • 仅使用在构造函数处
      • 告诉编译器,该构造函数,仅用于调用,不能用于转换构造使用

        explicit Fraction(int num, int den = 1)
        : m_numerator(num), m_denominator(den) {  }

智能指针(pointer-like classes)

//定义
template<class T>
class shared_ptr
{
public:T& operator*() const{ return *px;  }T* operator->() const{ return px; }shared_ptr(T* p) :px(p){}
private:T* px;long* pn;
};
//使用
struct Foo
{....void method(void){....}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();//实际通过智能指针返回了Foo的指针
//让对象sp,也具有了指针一样的使用方法
//关于->符号,有一个特殊行为,当作用下去后,得到对象后,不会被消耗掉
  • 智能指针中一定持有一个真正的指针

仿函数(function-like classes):重载()操作符

/定义部分
template <class T>
struct identity
{const T&;operator() (const T& x) const { return x; }
};
template <class Pair>
struct select1st
{const typename Pair::first_type& operator()(const Pair& x) const {return x.first;}
};
template <class Pair>
struct select2nd
{const typename Pair::second_type& operator()(const Pair& x) const {return x.second;}
}
//pair部分
template <class T1, class T2>
struct pair
{T1 first;T2 second;pair(): first(T1()), second(T2()){}pair(const T1& a, const T2& b):first(a), second(b){}
}
//使用部分
select1st <pair>() ();
//第一个括号:创建对象
//第二个括号:调用操作符()重载

namespace

  • 通过namespace可以将部分内容区分开,不需要考虑多人协作时,类、函数名称冲突的情况,只需要自己将自己的代码用namespace包起来即可
    .....
    //定义namespace
    namespace storyDog
    {......void function1(){....}......
    }
    .....
    ......
    //使用
    storyDog::function1();
    ......

模版

  • 类模版(class template)
    //定义部分
    template <typename T>
    class complex
    {
    public:complex(T r = 0, T i = 0):re(r), im(i){}complex& operator += (const complex&);T real() const { return re; }T imag() const { return im; }
    private:T re, im;
    }
    {
    //使用的时候指定T的类型
    complex<double> c1(2.5, 1.5);
    complex<int> c2(c2, 6);
    }
  • 函数模版(function template)
    //定义
    template <class T>
    inline const T& min(const T& a, const T& b)
    {return b < a? b: a;
    }
    class stone
    {
    public:stone(int w, int h, int weight):_w(w), _h(h), _weight(weight){}bool operator< (const stont rhs) const{return _weight < rhs._weight;}
    private:int _w, _h, _weight;
    }
    //使用
    stone r1(2, 3), r2(3, 3);
    r3 = min(r1, r2);
    //编译器会对function template进行实参推倒(argument deduction),无需手动指定具体类型是什么。
  • 成员模版(member template)

    template <class T1, class T2>
    struct pair{typedef T1 first_type;typedef T2 second_type;T1 first;T2 second;pair():first(T1()), second(T2()){}pair(const T1& a, const T2& b):first(a),
    second(b){}//成员模版(在模版中的模版)//可以更细微的使用模版,不需要与类模版相同template<class U1, class U2>pair(const pair<U1, U2>& p):first(p.first), second(p. second){}
    }
    • 考虑带有子父类模版的情况

      template<typename _Tp>
      class share_ptr:public __shared_ptr<_Tp>
      {...template<typename _Tp1>explicit shared_ptr(_Tp1* __p): __shared_ptr<_Tp>(__p){}...
      };
        Base1* ptr = new Derived1; //up-castshared_ptr<Base1> sptr(new Derived1);  //模版up-cast
  • 模版特化(Specialization)
    //泛化的版本
    template<class key>
    struct hash();
    //特化的版本
    template<>
    struct hash<long>{size_t operator()(long x) const { return x;}
    };
    cout << hash<long>()(1000);
  • 模版的偏特化(partial specialization)
    • 个数上的偏特化
      template<typename T, typename Alloc=...>
      class vector
      {
      ....
      }
      ....
      template <typename Alloc = ....>
      //绑定了其中的一个,另外一个没有被特化
      class vector<bool, Alloc>
      {
      ....
      }
    • 范围的偏特化
      //非指针用这套代码
      template<typename T>
      class C{....};
      //指针用这套代码
      template <typename T>
      class C<T*>{....};
  • 模版模版参数(template template parameter)
    //定义
    template<typename T,template <typename T> class Container>
    //含义:第二个模版接受一个Container
    //并且,这个Container的模版接受第一模版参数作为他的模版参数
    class XCls
    {
    private:Container<T> c;
    public:....
    };
    //使用
    template<typename T>
    using Lst = list<T, allocator<T>>;
    XCls<string, Lst> mylst;

    浅谈C++11

  • 可变数量的模版参数(variadic templates)
    void print(){}
    //定义
    template<typename T, typename... Types>
    void print(const T& firstArg, const Types&... args) 
    {cout << firstArg << endl;print(args...);//递归调用,来解析模版参数包
    }
    .......
    {//调用print(7.5, "hello", bitset<16>(377), 42);
    //想要知道后续的参数包的大小,可以使用sizeof...(args)来获得
    }
  • auto关键字
    list<string> c;
    auto ite = find(c.begin(), e.end(), target);
    //通过auto来声明变量类型
    //原始的是list<string>::iterator ite;现在简化为auto即可。

    注意:使用auto声明变量,必须初始化,否则会报错。

  • ranged-base for
    • 对于可以遍历的对象,可以简化的来写for循环
      for(decl :coll){statement
      }
    • pass by value(不会影响原始容器的值)
      vector<double> vec;
      for(auto elem: vec){
      cout << elem << endl;
      }
    • pass by reference(可以改变原始容器的内容)
      vector<double> vec;
      for(auto& elem: vec){
      elem += 3;//可以改变原容器的内容
      }

      reference

      引用(reference):就是对象的另一个名字。(名字是名词,所以此时我们把引用当做一个名词)。引用主要用作函数的形式参数。(作为参数的,那更是名词了)。到此为止,接下来就好理解了,因为它是个名词,对于名词的理解就比动词的理解方便多了。
      进一步理解:引用是一种复合类型(引用又是一种类型),通过在变量名前添加”&“符号来定义。复合类型指的是用其他类型来定义的类型。
      结论:其实引用只是一个别名,即只是他绑定的对象的另一个名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上

关于面向对象的问题

关于这个问题啊,我之前的笔记写的很详细,在这里也不想再赘述了。链接:
http://www.jianshu.com/p/34a30505176d

这篇关于(Boolan) C++面向对象高级编程(四)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/914223

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝