-
重载、覆盖和隐藏的区别
重载(overload) :就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
多个重载函数在调用的时候根据函数的参数来区别不同的函数。
关键点:函数名相同,参数表不同覆盖(override) :是指在派生类中重新对基类中的虚函数重新实现,即函数名和参数都一样,只是函数的实现体不一样。
关键词:派生类中,虚函数,函数名和参数表完全相同隐藏(hide) :派生类中的函数把基类中相同名字的函数屏蔽掉了。
隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么我们就说这个派生类的成员函数"隐藏"了基类的成员函数,也就是说它阻止了编译器继续向上查找函数的定义.关于这三种情况的示例代码如下
#include <iostream.h> class Base { public: virtual void f(float x) { cout << "Base::f(float) " << x << endl; } void g(float x) { cout << "Base::g(float) " << x << endl; } void h(float x) { cout << "Base::h(float) " << x << endl; } }; class Derived : public Base { public: virtual void f(float x) { cout << "Derived::f(float) " << x << endl; } void g(int x) { cout << "Derived::g(int) " << x << endl; } void h(float x) { cout << "Derived::h(float) " << x << endl; } };
从以上代码能够看出:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。考虑代码在运行时的结果:
void main(void) { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 (surprise!) // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 (surprise!) pd->h(3.14f); // Derived::h(float) 3.14 }
在第一种调用中,函数的行为取决于指针所指向的对象。在第二第三种调用中,函数的行为取决于指针的类型。所以说,隐藏破坏了面向对象编程中多态这一特性,会使得开发人员产生混乱。
函数接口设计注意要点
在设计基类的函数接口时,不要出现既是虚函数,又对该虚函数进行了重载的情况。这样会对派生类的多态函数实现造成不便。
示例代码如下class Base { virtual foo(); virtual foo(int n); } class Derived : public Base { virtual foo(); }
从以上代码能够看出:
-
Derived::foo()
与Base::foo()
构成了多态关系
-
Derived::foo()
隐藏了Base::foo(int)
所以,如果派生类希望实现
foo()
函数的多态,也就必须实现foo(int)
的多态,否则就会出现隐藏。
如果在现有代码中已经发生了上述Base
的情况,在不改变原有代码的情况下,可以使用在派生类中使用这样的语法using Base::foo;
,这会将基类中所有的名为foo
的函数引入到派生类中来。 -
-
关于Qt中使用中文编码的一些问题
Qt库作为一个开源库,并且支持多语言。在开发时需要考虑字符编码问题。
我现在的使用Qt开发环境为VS2012+Qt5.1.1,所以一下所讨论的也是基于这个版本而言的。目前Qt5已经将tr()
删掉了。在windows下使用Qt库进行开发
有两种主流开发环境:- 使用Qt Creator作为开发环境
- 使用Visual Studio + Visual Assist + Qt Visual Studio Addon作为开发环境
目前我使用的就是第二种开发环境,鉴于在Windows平台下没有哪个开发环境能与Visual Studio相媲美。
使用VS开发Qt程序时需要注意源文件的编码格式
Qt5官方推荐的源文件编码格式为UTF-8,QString内部的编码格式就是UTF-8,使用QtCreator创建的源文件的编码格式也是UTF-8 without BOM。
但是VS在中文操作系统上,如果不进行特殊的设置,默认创建的含有中文的源文件编码格式为gb3212。这样的原始字符串就是gb2312格式的,在使用时需要进行特殊转换,这样显示在界面上才不会乱码。
推荐使用一下两种方法解决gb2312编码问题:-
QString::fromLocale8Bit()
会将gb2312编码的字符串转换为UTF-8格式以便存放在QString中。
-
QStringLiteral()
是一个宏定义,会在编译期将字符串实例化,对静态字符串使用这个字符串也是一个提高效率的方法(因为减少了运行时的内存申请开销)。
在VS中使用utf-8 with BOM的源文件格式
如果使用了utf-8 with BOM的源文件格式,VS会将其中的字符串转换为gb2312编码的中文,这样是为了兼容旧版本的编译器。
可以使用编译选项让VS编译器不进行这项转换,这样就可以直接使用字符串char*
初始化QString了。- 微软在VS2010中提供了
#pragma execution_character_set("utf-8")
这样的编译选项,能够防止编译器进行文件编码转换,保证字符串保留utf-8格式。
- VS2012 并不支持以上编译选项,VS2012将这个特性取消了
- VS2015 可以使用
/utf-8
让编译器能够识别 utf-8 without BOM格式的文件,并保留utf-8的字符串编码。
C++11的编码格式支持
对于支持C++11标准的编译器,可以采用如下的写法产生utf-8格式的字符串。const char* szMsg = u8"字符串";
- 使用Qt Creator作为开发环境
-
为git设置socks5代理
在某些时候,为了绕过网络封锁,在使用git时需要借助代理完成相关网络操作。
一个可用的VPN代理是先决条件
关于VPN的架设就不在此讨论了。
将repo的链接由ssh改为为https
已经使用https的repo跳过此步骤。
本教程需要通过https(http)的方式使用代理,所以如果现有的repo是使用ssh方式clone到本地的,需要修改其为https方式。
需要修改repo_name/.git/config
中的url配置,将其中的git格式改为https格式。使用文本编辑工具将其打开进行修改,以github为例,需要将git@github.com:user_name/repo_name.git
改为https://github.com/user_name/repo_name.git
。
可以使用sed指令完成替换。sed -i 's/git@github.com:/git:\/\/github.com\//' ./.git/config
设置git使用代理
将git配置为使用socks5代理,以socks5的ip和端口是
127.0.0.1:1080
为例。git config --global http.proxy 'socks5://127.0.0.1:1080' git config --global https.proxy 'socks5://127.0.0.1:1080'
将登录信息缓存下来
使用https时,在向远程仓库进行推送(git push)时会要求验证用户名和密码,由于每次都输入验证信息是十分繁琐的,考虑将验证信息缓存下来。
git config --global credential.helper cache
git默认会将密码缓存15分钟,在这期间进行的操作是不需要再次进行密码验证的,可以使用下面命令修改缓存的时长。
git config --global credential.helper "cache --timeout=3600"
将登陆信息保存下来
也可以考虑将用户名和密码永久保存下来,这样就不需要再次进行密码验证了。
注意: 使用这种配置会将明文的账户和密码保存在~/.git-cretidentials
文件中,所以仅推荐在个人电脑中使用这个配置。git config --global credential.helper store
参考资料
(全文完)
-
windows平台下的C++开发框架
在开发windows应用程序时,经常会遇到win32、mfc、atl的函数。下面就对三种API(或者说是开发框架)进行了总结。
Win32开发框架
win32开发框架是最基础的函数接口,更接近操作系统底层。
使用Win32接口的优点是程序的依赖最小,生成的可执行文件的体积也最小。MFC开发框架
MFC是Microsoft Foundation Class的缩写,将win32的相关接口按照OOP的思想封装了起来,目的是提供一个快速开发原生应用的框架。
使用MFC开发的程序体型略显臃肿,而且MFC也比较陈旧了,并不推荐使用MFC进行开发。ATL开发框架
ATL是Active Template Library的缩写,是针对COM组件的开发实现的一套开发框架。ATL简化了ActiveX COM组件的开发。
-
使用emacs在目录中递归搜索字符串
对文件中的字符串搜索是开发中经常遇到的情景,下面就对常见的搜索字符串的方式进行了总结。
对当前buffer进行搜索(单个文件)
C-s
向后搜索C-r
向后搜索M-s o
使用occur对当前buffer中的指定关键字进行统计
在安装了helm以后可以使用helm-occur
完成当前buffer的搜索对目录中的指定文件进行搜索(一个目录下的多个文件)
C-x d
进入dired模式,在dired模式中使用M-x <dired-do-find-regexp>
(快捷键A
)对标记的文件进行搜索。对目录中的所有文件进行递归搜索(一个目录下的所有文件和所有子目录中的文件)
M-x <rgrep>
能够对一个目录下所有的文件进行正则表达式匹配注意事项
在windows平台上使用grep和find指令时,需要借助仿linux环境中的程序完成,如cygwin中的程序。
需要将程序的路径添加到系统环境变量path中,并且为了覆盖windows系统自带的find命令,需要将该路径放在系统find路径之前。
tips: 对于安装了git bash
的同学,直接将其中的mingw64\bin
的绝对路径加入系统path中即可。 -
ss服务器的配置指南
租一个服务器自己搭建梯子是科学上网方便稳定快捷的好方法。
本文主要是自己部署vpn服务端的笔记,作为备忘留存下来。安装ss服务程序
安装环境为CentOS7, 需要使用python的包管理工具pip完成对ss-server的安装。
sudo yum install m2crypto python-setuptools sudo easy_install pip sudo pip install shadowsocks
配置ss服务器
配置文件示例 :
这份配置中使用了多个端口作供ss-server使用{ "server":"your-server-ip", "local_address":"127.0.0.1", "local_port":1080, "port_password": { "32366":"shadowsocks", "32367":"shadowsocks", "32368":"shadowsocks", "32369":"shadowsocks", "52369":"shadowsocks" }, "timeout":300, "method":"aes-256-cfb", "fast_open": false }
启动脚本示例 :
直接在命令行直接输入指令也能够完成ss服务的启动和停止,使用脚本只是为了方便一些。#!/bin/bash ss_start() { ssserver -c /root/shadowsocks/shadowsocks.json -d start } ss_stop() { ssserver -c /root/shadowsocks/shadowsocks.json -d stop } # shell script main if [ "$#" -eq "0" ]; then ss_start else case "$1" in start) ss_start exit;; stop) ss_stop exit;; esac fi
增加系统启动项
可以将启动脚本加入系统启动项,这样就能够保证在每次reboot后,服务都能自动开启。 -
常用git操作备忘
对常用指令进行总结,方便以后遇到相同情况时查阅。
-
使用emacs查找替换指定目录所有文件中字符串
本文主要介绍在emacs中对一个目录中的很多文件进行字符串查找和替换操作的方法。参考了 XahLee 的教程后,在此记录做个备份。
操作步骤
进入dired模式 : 我的快捷键配置为
C-x d
, 也可以使用M-x <dired>
.
标记需要查找的文件 : 在进入dired模式后,对需要操作的文件或目录进行操作。-
m
对光标所在的文件或目录进行标记
-
u
对光标所在的文件或目录取消标记
-
U
取消所有标记
可以通过
M-x <dired-mark-files-regexp>
使用正则表达式完成批量文件增加标记。
批量查找 : 使用M-x <dired-do-query-replace-regexp>
进行查找替换。在有grep
的支持时(linux环境下或windows环境中增加了类似cygwin的程序),也可以使用<dired-do-find-regexp-and-replace>
进行文本查找替换。
查找结果 : 在查找结果中使用如下快捷键完成操作-
y
对当前记录执行替换操作
-
n
跳过当前记录
-
C-g
停止本次查找替换操作
-
!
同意本文件中的查找替换操作
-
N
跳过本文件中的查找替换操作
-
Y
同意本次所有的查找替换操作
相关技巧
如果需要批量查找(不进行替换),可以在dired模式中
M-x <dired-do-search>
进行查找,对于查找结果使用M-x tags-loop-continue
进行逐条浏览。 -
-
STL容器的迭代器失效问题
1 引言
常见的STL容器都能使用迭代器访问容器内元素,迭代器相同于一个指向容器内元素的指针,可以通过移动迭代器实现遍历容器。
但是在使用迭代器时需要考虑STL容器的失效问题;迭代器失效主要出现在对容器进行了增删操作之后,迭代器不再指向原本的元素。
这时如果再通过迭代器访问容器就有可能出现异常。2 map容器
内部数据结构 :红黑树
插入操作 :插入操作会申请新的节点空间,然后加入都红黑树中,原来的迭代器指向的内存空间都未改变,故不会出现迭代器失效。
删除操作 :删除操作只会引起被删除节点的迭代器失效。3 vector容器
内部数据结构 :数组(一段连续内存空间)
插入操作 :由于vector使用的是一段有长度限制的连续空间,插入(push_back
/insert
)操作在vector中加入新的元素时需要分情况考虑。- 当vector中元素总数仍不大于capacity,这时插入位置后的元素都被依次移动到下一个位置,所以插入位置之后的迭代器都会失效。
- 当vector中元素总数大于capacity,这个时候会重新开辟更大的内存空间,将原来的vector中的内容复制到新的vector中,回收原先vector的内存空间。由于新的vector的地址已完全改变,所以原先的所有迭代器都会失效。
删除操作 :删除(
pop_back
/erase
)操作在vector中删除元素,删除位置后的元素都被依次复制到前一个位置,所以删除位置之后的迭代器都会失效。
示例代码:4 list容器
内部数据结构 :双向环状链表
插入操作 :插入操作会申请新的节点空间,然后加入到链表中,原来的迭代器指向的内存空间都未改变,故不会出现迭代器失效。
删除操作 :删除操作只会引起被删除节点的迭代器失效。5 小结
在操作STL容器时,对容器增删(
erase
/insert
)之后应该注意接收返回值,这样可以有效避免迭代器失效的产生。
对容器进行遍历删除操作时,另一种语法技巧是在erase
接口中对迭代器进行后自增(it++),这种写法也能够保证遍历正常进行。理解这种写法需要参考运算符重载的相关知识,后自增操作会产生一个临时变量用于函数返回。 - 当vector中元素总数仍不大于capacity,这时插入位置后的元素都被依次移动到下一个位置,所以插入位置之后的迭代器都会失效。
-
关于size_t和size_type的比较
1 引言
编程中常见的表示两种大小的类型标识符有
size_t
和size_type
,这两种类型十分相似,非常容易混淆。本文对他们的使用场景进行了比较,并提供了示例代码。2
size_t
类型当需要描述某个对象的大小(size)时都应该使用
size_t
,其大小被限制为 0 到SIZE_MAX
之间。size_t
的定义在<cstddef>
中,SIZE_MAX
的定义在<climits>
中。size_t
一般会作为sizeof
运算符的返回结果,可以理解为在给定的平台中是统一的。3
size_type
类型在
STL
中定义了size_type
是与Allocator::size_tpye
相对应的。而Allocator
是模板的一个参数。size_type
可以理解为在给定容器范围内是统一的。4 二者之间的比较
这两种类型主要是为了提高代码的可移植性。一般情况来说,这两种类型是没有区别的,因为使用标准库默认的
Allocator
所对应的size_type
就是size_t
。5 示例代码
注意
size_type
在使用时需要加上容器类型限定。std::size_t n1 = sizeof(int); std::vector<int> v; std::vector<int>::size_type n2 = v.size(); std::string s; std::string::size_type n3 = s.size();
-
常见的由于语法不当引起的编译警告
1 引言
本文主要总结了笔者自己的开发中遇到的不规范的代码写法。这些代码风格可能并不会引起错误,但是会产生隐患或容易引起歧义。
这些语法缺陷可以通过合理配置编译器的编译选项检查出来,对于编译器的警告配置在前面有文章进行了简单介绍。 -
在emacs中使用GNU Global的配置指南
1 引言
本文主要针对emacs中的global的配置和使用方法做了简要介绍。global是代码标签化索引工具,能够在许多源码中快速定位函数的实现位置。我主要使用global来辅助代码跳转;还有一种用法是用来辅助代码补全,目前正在研究中。