
暂无个人介绍
能力说明:
了解Docker是什么,能做什么,产生的背景,理念是怎样。熟悉基本的Docker用法,知道怎么通过帮助命令来完成相应的操作,搞清楚一个完整的Docker有哪几个部分组成。
暂时未有相关云产品技术能力~
阿里云技能认证
详细说明本文演示ubuntu20.04下安装Thrift并配置CPP和Python3的使用环境官方教程链接:Ubuntu/Debian install,Building From Source先安装好 g++ 和 python3sudo apt update sudo apt install g++ sudo apt install python3安装 Thrift安装相关依赖包sudo apt-get install automake bison flex g++ git libboost-all-dev libevent-dev libssl-dev libtool make pkg-config安装python packagessudo apt install python-all python-all-dev python-all-dbg下载 Thrift 并解压wget https://dlcdn.apache.org/thrift/0.15.0/thrift-0.15.0.tar.gz tar -xf thrift-0.15.0.tar.gz执行命令cd thrift-0.15.0/ ./configure执行完后最后的输出内容如下,yes即代表将支持的语言执行命令sudo make //此步骤花费时间稍长 sudo make install thrift -version //若正常输出Thrift的版本则证明安装完成常见问题找不到动态链接库报错类似 ./main: error while loading shared libraries: libthrift-0.15.0.so: cannot open shared object file: No such file or directory配置 /etc/ld.so.conf 文件,否则可能会报找不到动态链接库等错误执行命令vim /etc/ld.so.conf添加内容 /usr/local/lib,添加后文件内容如下执行命令使添加的内容生效sudo /sbin/ldconfigpython找不到thrift模块报错内容类似:ModuleNotFoundError: No module named 'thrift'可通过pip安装thrift解决,若未安装pip,先执行安装pip的命令sudo apt install python3-pip然后执行sudo pip install thrift即可解决找不到thrift模块的问题
Halo博客安装安装Docker本文使用全新安装的系统,因此并没有安装Docker,已安装Docker则可跳过这一步。登录终端输入以下内容并按回车等待即可完成Docker安装,该命令同样适用于Ubuntu。curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun创建Halo博客的工作目录本文将Halo博客工作目录置于home目录下,并命名为.halo。该目录将用来存放博客所有配置、文章、主题等数据,若有备份或迁移需要则可以直接复制该目录。创建目录并前往该目录mkdir ~/.halo && cd ~/.halo下载默认配置文件到工作目录wget https://dl.halo.run/config/application-template.yaml -O ./application.yaml该配置文件包含了端口,数据库,缓存,后台根路径等配置,一般下载后不用更改,若需要更改使用的数据库等需求可参考官方文档。拉取Halo镜像docker pull halohub/halo:latest创建容器docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --restart=unless-stopped halohub/halo:1.4.118090:8090第一个8090为宿主机端口,第二个8090为容器端口,含义为将宿主机的8090端口映射到容器的8090端口。~/.halo:/root/.halo含义为将宿主机的~/.halo目录映射到容器的/root/.halo目录,注意后者不可更改。其他参数具体含义请参考官方文档。此时即可使用http://ip:端口号访问安装引导界面。但比较推荐完成下列反向代理以及SSL证书配置后再进行博客的初始化。使用Nginx进行反向代理注意下文内容基于使用8090宿主机端口,如有更改请注意在配置文件中将8090改为自行修改的端口安装Nginx# 下列代码适用于CentOS # 添加 Nginx 源 sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm # 安装 Nginx sudo yum install -y nginx # 启动 Nginx sudo systemctl start nginx.service # 设置开机自启 Nginx sudo systemctl enable nginx.service# 下列代码适用于Ubuntu sudo update sudo apt install nginx配置Nginx# 下载 Halo 官方的 Nginx 配置模板 curl -o /etc/nginx/conf.d/halo.conf --create-dirs https://dl.halo.run/config/nginx.conf下载之后需要对其进行修改。以上方法安装的Nginx默认路径为/etc/nginx# 适用 vim 编辑 halo.conf vim /etc/nginx/conf.d/halo.conf打开之后的内容应该类似server { listen 80; server_name example.com www.example.com; # 将 example.com www.example.com 修改为自己的域名 location / { proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8090/; # 如有更改默认的宿主机8090端口,请修改此处为执行选择的端口 } }修改完成之后# 检查配置是否有误 sudo nginx -t # 重载 Nginx 配置 sudo nginx -s reload使用 certbot 申请并自动安装SSL证书阿里云/腾讯云等云服务商也可以申请免费的SSL证书并下载,这类证书请查阅相关教程,本文仅介绍通过certbot申请证书并自动安装。安装 certbot 和 certbot nginx 插件:# 根据自己的系统选择相应命令 # CentOS 安装 certbot 以及 certbot nginx 插件 sudo yum install certbot -y sudo yum install python3-certbot-nginx -y # Ubuntu 安装 certbot 以及 certbot nginx 插件 sudo apt install certbot sudo apt install python3-certbot-nginx申请并自动配置证书:sudo certbot --nginx需要输入邮箱并按y或a同意相关协议,具体请参考输入命令后的输出自动续约这里申请的免费证书可以免费续期,理论上可以一直免费使用,这里提供了自动执行续期的命令sudo certbot renew --dry-run结束语到这里,即完成了所有的步骤,可以直接通过域名进行Halo博客的访问。注意,国内服务商注册的域名大多需要备案,否则无法使用,具体请参考云服务商的文档。在设置完反向代理后,必须在博客的后台管理界面设置正确的博客地址,否则可能导致CSS加载不成功、样式混乱等错误。
ubuntu安装tmuxapt update apt install tmuxtmux可以做到类似分屏的功能,而且在终端连接非正常断开时,tmux的进程不会被杀,从而保留了在tmux中正在编辑的代码等tmux可以开多个session,一个session可以包含多个window,而一个window可以包含多个pane。简而言之,终端中“分屏”出来的一个个小窗口就是一个pane,整一个终端的大窗口就是一个window。命令:tmux:新建一个session和一个windowtmux a:打开挂起的sessiontmux ls:显示所有挂起的session在tmux中的操作:按下 ctrl + b 后松开,再按 % 可将当前pane左右平分成两个pane按下 ctrl + b 后松开,再按 " 可将当前pane上下平分成两个pane按下 ctrl + b 后松开,再按 d 可挂起当前的session按下 ctrl + b 后松开,再按 z 可将当前pane全屏或取消全屏按下 ctrl + b 后松开,再按 c 可在当前session中创建新的window按下 ctrl + b 后松开,再按 s 可选择其他session,用方向键操作按下 ctrl + b 后松开,再按 w 可选择其他window,同样用方向键操作按下 ctrl + b 后松开,再按 PageUp 或 PageDown 可翻阅当前pane中的内容tmux中的复制粘贴按下 ctrl + b 后松开,再按 [用鼠标选中文本,被选中的文本会被自动复制到tmux的剪贴板按下 ctrl + b 后松开,再按 ],会将剪贴板的内容粘贴到光标处一个小tips:如果觉得 ctrl + b 两个键隔太远,可以修改这个组合建,这里演示修改成 ctrl + a 的操作方法在home目录下新建一个名为.tmux.conf的文件,并添加如下文本(最后一行为加快esc在tmux中的响应,可以自行选择是否保留)。set -g prefix C-a unbind C-b bind C-a send-prefix set -g escape-time 20在home目录下输入tmux source .tumx.conf即可完成修改
简介命令行下的文本编辑器,能够根据扩展名识别编程语言,支持自动缩进和代码高亮。使用方法:vim filename若该文件不存在,则打开一个新文件命名为filename共有三个模式以实现不同的操作一般命令模式:按指定的键可以实现复制、粘贴、选择、删除等操作编辑模式:在一般命令模式下按 i 键会进入编辑模式,此时可对文件进行编辑,编辑模式下按 esc 键会退出编辑模式返回一般命令模式命令行模式:在一般命令模式下按 : / ? 中的任意一个可以进入命令行模式,输入相关命令可执行相应的操作,如查找、替换、保存、退出等一般命令模式操作i :进入编辑模式h 或 左方向键 : 光标向左移动一个字符j 或 下方向键 : 光标向下移动一个字符k 或 上方向键 : 光标向上移动一个字符l 或 右方向键 : 光标向右移动一个字符数字n+空格 :光标向右移动这一行的n个字符数字n+回车 :光标向下移动n行0 或 home键 :光标移动到本行开头$ 或 end键 :光标移动到本行末尾字母G :光标移动到最后一行字母gg(连按两次g):光标移动到第一行数字n+字母G :光标移动到第n行/word :向光标之下寻找第一个值为word的字符串?work :向光标之上寻找第一个值为word的字符串v :选中文本,按下后可用方向键或上述移动光标的命令选择文本y :复制选中的文本d :删除选中的文本dd :剪切光标所在行yy :复制光标所在行p :将复制的数据在光标下一个位置/下一行粘贴u :撤销ctrl + r :取消撤销ctrl + q :取消当前正在执行的命令,vim卡住时可尝试> :将选中的文本向右缩进< :将选中的文本向左缩进ggdG :组合键,删除全文gg=G :将全文代码格式化命令行模式操作:q 退出:q! 不保存修改强制退出:w 保存:wq 保存并退出:set nu 显示行号:set nonu 隐藏行号:set paste 设置成粘贴模式,取消自动缩进:set nopaste 取消粘贴模式,开启自动缩进:noh 关闭查找关键词高亮:n n为数字,将光标移动到第n行,同 nG:n1,n2s/word1/word2/g n1与n2为数字,在第n1行与n2行之间寻找word1这个字符串,并将该字符串替换为word2:1,$s/word1/word2/g 将全文的word1替换为word2:1,$s/word1/word2/gc 将全文的word1替换为word2,且在替换前要求用户确认异常处理每次用vim打开文件时,会自动创建一个.filename.swp的文件用来保存临时数据,在正常退出文件时该临时文件会被删除,若不正常退出则该文件会存在。用vim打开文件时若.filename.swp这个临时文件已存在,则会报错,可按提示进行操作。或者,1.找到正在编辑该文件的vim并退出,2.直接删掉该swp临时文件
本文共介绍以下内容:expr,read,echo,printf,test命令与判断符号[]expr命令expr命令用于求表达式的值,格式为:expr 表达式表达式使用方法:用空格隔开每一项用反斜杠放在shell特定的字符前面(即转义)对包含空格和其他特殊字符的字符串要用引号括起来expr会在stdout中输出结果,如果为逻辑关系表达式,结果为真stdout为1,否则为0expr也有exit code,如果为逻辑关系表达式,结果为真则exit code为0,否则为1字符串表达式length STRING:返回STRING的长度index STRING CHARSET:返回CHARSET中任意单个字符在STRING中最前面的字符位置,下标从1开始。如果STRING中没有CHARSET中任一字符,则返回0。substr STRING POSITION LENGTH:返回STRING字符串中从POSITION开始,长度最大为LENGTH的字串。如果POSITION或LENGTH为负数、0或非数值,则返回空字符串。示例:str="Hello World!" echo `expr length "$str"` # 注意 `` 不是单引号,表示获取 ``中command的stdout,输出12 echo `expr index "$str" aWd` #输出 7,下标从1开始 echo `expr substr "$str" 2 3` #输出 ell整数表达式expr支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。+ - :加减运算。两端参数会转换为整数,如果转换失败则会报错。* / %:乘除取模运算。两端参数会转换为整数,如果转换失败则会报错。( ):可以表示优先级,但需要反斜杠转义示例:a=3 b=4 echo `expr $a + $b` # 输出7 echo `expr $a - $b` # 输出-1 echo `expr $a \* $b` # 输出12,*需要转义 echo `expr $a / $b` # 输出0,整除 echo `expr $a % $b` # 输出3 echo `expr \( $a + 1 \) \* \( $b + 1 \)` # 输出20,值为(a + 1) * (b + 1)逻辑关系表达式|:如果第一个参数非空且非0,则返回第一个参数的值,否则返回第二个参数的值,但要求第二个参数的值也是非空或非0,否则返回0。如果第一个参数是非空或非0时,不会计算第二个参数。&:如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参为0或为空,则不会计算第二个参数。< <= = == != >= > :比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。( ):可以该表优先级,但需要用反斜杠转义示例:a=3 b=4 echo `expr $a \> $b` # 输出0,>需要转义 echo `expr $a '<' $b` # 输出1,也可以将特殊字符用引号引起来 echo `expr $a '>=' $b` # 输出0 echo `expr $a \<\= $b` # 输出1 c=0 d=5 echo `expr $c \& $d` # 输出0 echo `expr $a \& $b` # 输出3 echo `expr $c \| $d` # 输出5 echo `expr $a \| $b` # 输出3read命令read命令用于从标准输入(stdin)中读取当行数据。当读到文件结束符(ctrl+d)时,exit code为1,否则为0。可选参数:-p:后面可以接要输出的提示信息-t:后面接以秒为单位的数字,定义接受输入的等待时间,超时会自动忽略此命令示例:read name # 读入 name 的值 ubuntu # stdin echo $name # 输出 name 的值 read -p "Please input your name:" -t 30 name # 读入 name 的值,等待时间 30 秒echo命令echo命令用于输出字符串,格式为echo STRING显示普通字符串echo "hello ubuntu" echo hello ubuntu # 引号可以省略显示转义字符-e参数表示进行转义,部分转义字符必须加此参数才起作用。例如:\\ \a \b \c \d \e \f \n \r \t \v,其他转义字符可能不需要-e也可以转义echo "\"hello ubuntu\"" # 使用双引号会转义 echo \"hello ubuntu\" # 也可以不使用双引号,也会转义 # 若没有转义可以加参数 -e echo -e "Hi\n" # -e 开启转义 echo "ubuntu" :<<! 第五行和第六行输出的内容如下: Hi ubuntu !显示变量name=ubuntu echo "hi,$name" # 输出 hi,ubuntu显示不换行echo -e "hi \c" # \c 表示不换行 echo "ubuntu"输出结果为:hi ubuntu显示结果重定向到文件echo "hello ubuntu" > output.txt # 将显示结果以覆盖方式输出到output.txt中原样输出字符串,不进行转义或取变量name=ubuntu echo '$name\"'输出结果为:$name\"显示命令的执行结果(stdout)echo `date`输出结果为:2021年 9月 5日 星期日 19时05分20秒 CSTprintf命令printf命令用于格式化输出,类似C/C++中的printf函数,默认不在字符串末尾添加换行符。格式:printf format-string [arguments...]示例:脚本内容:printf "%10d.\n" 123 # 占10位,右对齐 printf "%-10.2f.\n" 123.123321 # 占10位,保留2位小数,左对齐 printf "My name is %s\n" "ubuntu" # 格式化输出字符串 printf "%d * %d = %d\n" 2 3 `expr 2 \* 3` # 表达式的值作为参数输出结果: 123. 123.12 . My name is yxc 2 * 3 = 6逻辑运算符 && 和 ||&&表示与,||表示或和expr命令中的逻辑运算相似,这两个逻辑运算符也具有短路原则:expr1 && expr2:当expr1为假时,跳过expr2的运算expr1 || expr2:当expr1为真时,跳过expr2的运算表达式的exit code为0时表示真,非零表示假test命令在命令行中输入man test,可以查看test命令的用法。test命令用exit code而不是stdout返回结果。0表示真,非零表示假。test命令和逻辑运算符合用做一个简单条件判断,例如:test -e test.sh && echo "exist" || echo "not exist" # 用于判断文件 test.sh 是否存在,存在则输出 exist,否则输出 not exist文件类型判断test -e filename # 判断文件是否存在 test -f filename # 判断是否为普通文件 test -d filename # 判断是否为目录文件权限判断test -r filename # 判断文件是否可读 test -w filename # 判断文件时否可写 test -x filename # 判断文件是否可执行 test -s filename # 判断文件是否非空整数间的比较test $a -eq $b # a是否等于b test $a -ne $b # a是否不等于b test $a -gt $b # a是否大于b test $a -lt $b # a是否小于b test $a -ge $b # a是否大于等于b test $a -le $b # a是否小于等于b字符串比较test -z STRING # 判断STRING是否为空,是则返回true test -n STRING # 判断STRING是否为非空,是则返回true,( -n 可以省略) test str1 == str2 # 判断str1是否等于str2 test str1 != str2 # 判断str1是否不等于str2多重条件判定test -r filename -a -x filename -a :两条件是否同时成立-o:两条件是否至少一个成立!: 取反。 如 test !-x file当file不可执行时,返回true判断符号[][]与test的用法几乎一模一样,常用于if语句中,另外还有[[]],可以认为是一个加强版,支持更多特性,这里暂时不介绍。[ 2 -lt 3 ] # 同样返回值为 0 时代表真。注意 [ 之后有空格,]之前也有空格,否则会报错与test类似,[]也可以与逻辑运算合用做简单的条件判断[ -e test.sh ] && echo "exist" || echo "not exist"需要注意的有:[]内的每一项都要用空格隔开[]内的常量、变量最好都用双引号括起来,否则可能因常量、变量里有空格导致报错
派生类继承了基类的全部数据成员和除了构造、析构函数之外的全部函数成员,但是这些成员的访问属性在派生的过程中是可以调整的。从基类继承的成员,其访问属性由继承方式控制。基类的成员可以有 public、protected 和 private 三种。基类的自身成员可以对基类中任何一个其他成员进行访问,但是通过基类的对象就只能访问该类的公有成员。类的继承方式有 public、protected 和 private 三种。不同的继承方式导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。这里的访问有两个方面,一是派生类中的新增成员访问从基类继承的成员;而是在派生类外部(非类族内的成员)通过派生类的对象访问从基类继承的成员。公有继承当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而基类的私有成员不可以直接访问。 基类的公有和保护成员继承后仍为派生类的公有和保护成员,派生类的其他成员可以直接访问他们,类族之外则只能通过派生类的对象访问从基类继承的公有成员。派生类的成员或派生类的对象都无法直接访问基类的私有成员。下面用一个熟悉的 Point 类派生出一个新的 Rectang 类。矩形是由点加上长和宽构成的,点具备了 Point 类的所有特征,同时矩形也具有自身的特点(长、宽),这就需要在继承 Point 类的同时添加新的成员。//Point.h #ifndef _POINT_H #define _POINT_H class Point { public: void initPoint(float x = 0, float y = 0) {this->x = x; this->y = y;} void move(float offX, float offY){x += offX; y += offY;} float getX() const {return x;} float getY() const {return y;} private: float x, y; }; #endif //Rectangle.h #ifndef _RECTANGLE_H #define _RECTANGLE_H #include "Point.h" class Rectangle: public Point { public: void initRectangle(float x, float y, float w, float h) { initPoint(x, y); this->w = w; this->h = h; } float getH() const {return h;} float getW() const {return w;} private: float w, h; }; #endif //主程序 #include <iostream> #include <cmath> #include "Rectangle.h" using namespace std; int main() { Rectangle rect;//定义 Rectangle 类的对象 rect.initRectangle(2, 3, 20, 10);//设置矩形的数据 rect.move(3, 2);//移动矩形的位置 cout << "The data of rect(x, y, w, h):" << endl; cout << rect.getX() << "," << rect.getY() << "," << rect.getW() << "," << rect.getH() << endl; return 0; } //程序输出内容为 The data of rect(x, y, w, h): 5,5,20,10私有继承当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。(本是公有和保护成员)即基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类族外部通过派生类的对象无法直接访问它们。(本是私有成员)无论是派生类的成员还是通过派生类的对象都无法直接访问从基类继承的私有成员。经过私有继承后,所有基类的成员都成为了派生类的私有成员或不可直接访问的成员,如果进一步派生的话,基类的全部成员就无法在新的派生类中被直接访问。因此私有继承之后,基类的成员再也无法在以后的派生类中直接发挥作用,实际上相当于中止了基类功能的继续派生,出于这种情况,一般情况下不使用私有继承。//Point.h #ifndef _POINT_H #define _POINT_H class Point { public: void initPoint(float x = 0, float y = 0) {this->x = x; this->y = y;} void move(float offX, float offY){x += offX; y += offY;} float getX() const {return x;} float getY() const {return y;} private: float x, y; }; #endif //Rectangle.h #ifndef _RECTANGLE_H #define _RECTANGLE_H #include "Point.h" class Rectangle: private Point { public: void initRectangle(float x, float y, float w, float h) { initPoint(x, y); this->w = w; this->h = h; } void move(float offX, float offY){Point::move(offX, offY);} float getX() const {return Point::getX();} float getY() const {return Point::getY();} float getH() const {return h;} float getW() const {return w;} private: float w, h; }; #endif //主函数部分 #include <iostream> #include <cmath> #include "Rectangle.h" using namespace std; int main() { Rectangle rect;//定义 Rectangle 类的对象 rect.initRectangle(2, 3, 20, 10);//设置矩形的数据 rect.move(3, 2);//移动矩形的位置 cout << "The data of rect(x, y, w, h):" << endl; cout << rect.getX() << "," << rect.getY() << "," << rect.getW() << "," << rect.getH() << endl; return 0; } //程序输出内容为 The data of rect(x, y, w, h): 5,5,20,10派生类 Rectangle 私有继承了 Point 类的成员,这时 Point 类中的公有和保护成员在派生类中都以私有成员的身份出现。派生类的成员函数和对象无法访问基类的私有成员(如 x, y)。派生类的成员仍可以访问从基类的公有和保护成员(如 initPoint 函数)。但在类外部通过派生类的对象无法访问到基类的任何成员(如基类的 getX() 和 getY() )。因此在私有继承的情况下为了保证基类的外部接口功能在派生类中也存在,就必须在派生类中声明同名的成员(如 move, getX, getY),利用派生类成员对基类成员对访问能力,实现这些原有基类成员的功能。根据同名隐藏的规则,在调用时即会自动调用派生类的函数。可以看到,与公有继承相比较,私有继承时只修改了派生类 Rectangle 的内容,基类和主函数没有做任何改动,这就是面向对象程序设计的封装性的体现。Rectangle 类的外部接口不变,内部成员的实现做了改造,但并没有影响到程序的其他部分,这正是对象程序设计重用与可扩充性的一个实际体现。保护继承保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可直接访问。 这样,派生类的其他成员则可以直接访问从基类继承来的公有和保护成员,但在类的外部无法通过派生类的对象直接访问它们。无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员。基类的保护成员可能被它的派生类访问,但是绝不可能被其他外部使用者(比如程序中的普通函数,与该派生类平行的其他类等)访问。这样,如果合理的利用保护成员,就可以在类的复杂层次关系中在共享与成员隐蔽之间找到一个平衡点,既能实现成员隐蔽,又能方便继承,实现代码的高效重用和扩充。如果 B 是 A 的派生类,B 的成员函数只能通过 B 的对象访问 A 中定义的保护成员,而不能通过 A 的对象访问 A 的保护成员。类型兼容规则类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。 通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,这样实际上公有派生类具备了基类的所有功能,凡是基类可以解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下的情况。派生类的对象可以隐含转换为基类对象派生类的对象可以初始化基类的引用派生类的指针可以隐含转换为基类的指针在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。若 D 类为基类 B 的公有派生类,则在基类 B 的对象可以出现的任何地方,都可以用派生类 D 的对象来代替。class B{...}; class D: public B{...}; B b1, *pb1; D d1; //派生类对象可以隐含转换为基类对象,即用派生类对象中从基类继承来的成员逐个赋值给基类对象的成员 b1 = d1; //派生类对象可以初始化基类对象的引用 B &rb = d1; //派生类对象的地址可以隐含转换为指向基类的指针 pb1 = &d1;由于类型兼容规则的引入,对于基类及其公有派生类的对象,可以使用相同的函数统一进行处理。因为当函数的形参为基类的对象(或引用、指针)时,实惨可以是派生类的对象(或指针),而没有必要为每一个类设计单独的模块,大大提高了程序的效率。这正是 C++ 的又一重要特色,即多态性。#include <iostream> using namespace std; class Base1 { public: void display() const {cout << "Base1::display()" << endl;} }; class Base2: public Base1 { public: void display() const {cout << "Base2::display()" << endl;} }; class Derived: public Base2 { public: void display() const {cout << "Derived::display()" << endl;} }; void fun(Base1 *ptr) { ptr->display(); } int main() { Base1 base1; Base2 base2; Derived derived; fun(&base1); fun(&base2); fun(&derived); return 0; } //输出内容为: Base1::display() Base1::display() Base1::display()这样,通过对象名.成员名或者对象指针->成员名的方式,就可以访问到各派生类中继承自基类的成员。可以将派生类对象的地址赋值给基类 Base1 的指针,但通过这个基类类型的指针只能访问到从基类继承的成员。尽管指针指向的不是 Base1 的对象,但是 fun 函数也只能访问到从 Base1 继承来的成员函数 display() 而不是 Base2 或者 Derived 的同名成员函数。因此,主函数中三次对 fun 函数的调用结果都是一样的——访问了 Base1 的 display() 函数。根据类型兼容规则,可以在基类对象出现的场合使用派生类对象进行替代,但替代之后的派生类仅仅发挥出基类的作用。
2021年10月
2021年09月
2021年08月