C++并不是一个带有一组守则的一体语言:他是从四个次语言( C、Object-Oriented C++、Template、STL ) 组成的联邦政府,每个次语言都有自己的规约。记住这四个次于语言你就会发现C++容易了解得多。
以上句为例,是通过预处理器处理而不是编译器处理,有可能ASPECT_RATIO 没进入记号表内,于是如果出现了编译错误,那么编译器会提示错误信息是 1.653 而不是 ASPECT_RATIO ,你会感到非常困惑。
解决方法是用常量替换宏const double AspectRatio = 1.653
这样编译器就可以看到ASPECT_RATIO ,而且使用常量会使代码量较小,因为预处理器只会盲目的替换而出现多份 1.653
string对象通常比char* 更好一点对于class的专属常量,为了限制作用域在class内,并且防止产生多个实体,最好使用static
1 . 如果你的编译器支持在类内对const static 整数类型声明时获初值,则使用
2 . 如果不支持,则在类内定义,在对应的实现文件中赋值
如果你需要在编译器就使用一个class常量值,则应最好改用枚举类型enum,且枚举不能用来取地址,不会为它分配额外的存储空间对于形似函数的宏,最好改用inline的模板函数
const出现在星号左边目标是指物是常量,出现在星号右边表示指针本身是常量,如果出现在两边,则指针和物都是常量void f1(const Widget* pw)和void f2(Widget const* pw)两种写法意义相同,都表示被指物是常量 对于STL迭代器来说,如果你希望迭代器所指的动科不可改动,你需要的是const_iterator 令函数返回一个常量值,往往可以降低因客户错误而造成的意外(例如把一个值赋值给一个返回值) 将const实施与成员函数的目的是为了明确该成员函数可作用于const对象:
1 . 他们使class接口比较容易理解
2 . 他们使得可以操作const对象
const成员函数和no-const成员函数可重载,即可以同时出现,在传入不同的参数时候会调用不同的版本,但是有时我们需要这样,但是又不想代码重复,我们可以在no-const成员调用const成员函数来处理这个代码重复问题 例如:const_cast<char &>( static_cast<const TextBlock&>(*this)[position]);,经过这样里面 先安全转型使得调用的是const版本,外面再去const转型
对于内置类型要进行手工初始化构造函数最好使用成员初值列表,不要在构造函数中使用赋值操作来初始化,而且初值列表列出的成员变量次序应该和在class中声明的次序一样,因为声明次序就是C++保证的初始化次序 对于static对象,在跨编译单元之间的初始化次序是不能确定的,因为C++只保证在本文件内使用之前一定被初始化了
举例(使用如下方式可以解决这个问题即以loacl static对象替换non-local static对象):
class FileSystem{...};
FileSystem& tfs(){
static FileSystem fs;
return fs;
}
如果你不定义,编译器会自动帮你实习默认的构造函数,析构函数,拷贝赋值运算符和拷贝构造函数,但是如下几种情况不会替你生成默认的拷贝赋值运算符
1 . 类中含有引用的成员变量
2 . 类中含有const的成员变量
3 . 类的基类中的拷贝赋值运算符是私有成员函数
当我们不希望编译器帮我们生成相应的成员函数的时候,我们可以将其声明为private并且不予以实现
以下情况应该为类声明一个virtual析构函数:
1 . 用来作为带有多态性质的基类的类
2 . 一个类中带有任何virtual函数
如果类的设计目的不是作为基类使用,那么就不应该为它声明virtual析构函数
析构函数不要吐出异常,如果实在要抛出异常,那么最好使用std::abort();,放在catch中,把这个行为压下去 如果某个动作可能会抛出异常,那么最好把它放在普通函数中,而不是放在析构函数里面,让客户来执行这个函数并去处理
在构造和析构的时候,不要试图调用或在调用的函数中调用virtual函数,因为会调用父类版本导致出现一些未定义的错误
解决办法之一:
class Transaction{
publci:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logIngo) const;//把它变成这样的non-virtual函数
...
};
Transaction::Transaction(const std::string& logInfo){
...
logTransaction(logInfo);//这样调用
}
class BuyTransaction: public Transaction{
BuyTransaction( parameters ):Transaction(createLogString( parameters )){...}//将log信息传给基类的构造函数
private:
static std::string createLogString( parameters );//注意此函数为static函数
}
为了实现连锁赋值如内置类型x= y = z =15由于=采用右结合律,所以等价于x = (y = (z = 15)),因此,为了使我们自定义类也实现,所以*重载=,+=,-=,*=使其返回refercence to this
在赋值的时候会出现对自我进行赋值的情况,这种情况下我们很容易写出不安全的代码
Widget::operator=(const Widget& rhs){
delete pb; //把自己释放了
pb = new Bitmap(*rhs.pb);//这就不安全了
return *this;
}
因此有三种推荐的做法
Widget::operator=(const Widget& rhs){
if(this == &rhs) return *this;//验证是不是相同
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
Widget::operator=(const Widget& rhs){
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);//让pb指向*pb的一个副本
delete pOrig; //删除原先的pb
return *this;
}
class Widget{
...
void swap(Widget& rhs);//交换*this和rhs的数据
...
};
Widget::operator=(const Widget& rhs){
Widget temp(rhs);//创建一个rhs副本
swap(temp);//交换*this和上面的副本
return *this;
}
为了确保复制的时候复制对象内的所有成员变量,我们应该在字类的构造和赋值函数中调用父类的构造和赋值函数来完成各自的任务 不要尝试在复制构造函数和赋值函数中相互调用,如果想消除重复代码,请建立一个新的成员函数,并且最好将其设为私有且命名为init
为了防止资源泄露,我们应该在构造函数中获取资源,在析构函数中释放资源,这样可以有效的避免资源泄露 使用智能指针是一个好的办法,在C++11中auto_ptr已经被弃用,有三个常用的是unique_ptr,share_ptr和weak_ptr
我们在管理RAII(构造函数中获得,析构函数中释放)观念的类时,应该对不同的情况,根据不同的目的进行处理
1 . 当我们处理不能同步拥有的资源的时候,可以才用禁止复制,如把copying操作声明为private
2 . 当我们希望共同拥有资源的时候,可以采用引用计数法,例如使用shared_ptr
3 . 当我们需要拷贝的时候,可以采用深拷贝
4 . 或者某些时候我们可以采用转移底部资源拥有权的方式
有的api函数往往需要访问类的原始资源,所以每一个RAII类应该提供一个返回其管理的原始资源的方法 返回原始资源可以使用显示转换也可以使用隐式转换,但是往往显示转换更加安全一点,但是隐式转换更加方便
class Font{
...
FontHandle get() const {return f;} //显示转换
...
operator FontHandle() const {return f;} //隐式转换函数
....
private:
FontHandle f; //管理的原始资源
}
不要对数组形式做typedef,因为这样会导致delete的时候调用的是delete ptr而不是delete [] ptr,对内置类型会出现未定义或有害的,对类的类型会导致无法调用剩余的析构函数,导致类中管理的资源无法释放,从而造成内存泄漏 在new 表达式中使用[ ] ,则在相应的delete 表达式中也使用 [ ]
诸如这样的语句processWidget (std::tr1::shared_ptr(new Widget),priority())
1 . 在先执行new Widget`语句和调用std::tr1::shared_ptr构造函数之间
2 . 不能确定priority函数的执行顺序,可能在最前面,也可能在他们的中间
我们接口应该替客户着想,考虑周全,避免它们犯错误。例如在向函数传递日期的时候,把日期参数做成类的形式,并且用static成员函数来返回固定的月份,避免用户参数写错 接口应该和内置接口保持一致,避免让客户感觉不舒服,这方面STL做的很好 tr1::shared_ptr支持定制型删除器,使用它可以防范跨DLL构建和删除的问题,可以用它来自动解除互斥锁
谨慎的设计一个类,应该遵守以下规范
尽量以pass-by-reference-to-const替换pass-by-value,因为前者通常比较高效,比如在含有类的传递时,避免了多次构造函数和多次析构函数的调用,大大的提高了效率 但是对于某些,比如内置类型,迭代器,函数调用等最好以值传递的形式
绝对不能返回指针或者一个引用指向一个临时变量,因为它存在栈中,一旦函数调用结束返回那么你得到的将是一个坏指针,也不能使用static变量来解决,你可以通过返回值 来解决
1 . 为了保证一致性
2 . 可以细微的划分访问和控制以及约束
3 . 内部更改后不影响使用
protected并不比public更具封装性
我们可以用non-member、non-friend函数来替换某些成员函数,可以增加类的封装性,包裹弹性和扩充性
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
在你没有定义swap函数的情况下,编译器会为你调用通用的swap函数,但是有的时候那并不是高效的,因为默认情况它在置换如指针的时候把整个内存都置换 我们采取一种解决办法 1. 在类中提供一个 public swap成员函数,并且这个函数不能抛出异常2. 在类的命名空间中提供一个non-member swap函数,并令它调用类中的swap函数 3. 如果你正在编写一个类而不是模板类,为你的class特化std::swap函数,并令它调用你的swap函数 4. 请在类中声明 using std::swap,让其暴露,使得编译器自行选择更合适的版本
定义一个变量,那么你就得承受这个变量的构造和析构的成本时间,所以在定义一个变量的时候我们应该尽可能的延后定义时间,在使用前定义,这样避免我们定义了却没有使用它,造成浪费
旧式转型是C风格的转型,C++中提供四种新式转型:
1 . const_cast 通常被用来将对象的常量性转除。它也是唯一有此能力的转型操作符
2 . dynamic_cast 主要用来执行“安全向下转型” ,也就是用来决定对某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作
3 . reinterpret_cast 意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个pointer to int转型为一个int。这一类转型在低级代码以外很少见。
4 . static_cast 用来强迫隐式转换,例如将non-const对象转换为const对象,或将int转为double等等,它也可以用来执行上述多种转换的反向转换,例如将void* 指针转为 type 指针,将pointer-to-base 转为 pointer-ro-derived 。但它无法将 const 转为 non-const ——这个只有const_cast才能办到
旧式转型使用的时机是,当要调用一个explicit构造函数对一个对象传递给一个函数时,其他尽量用新式转型
请记住以下:
1 . 如果可以的话,避免dynamic_cast转型,如果实在需要,则可以试着用别的无转型方案代替
2 . 如果转型是必要的,那么应该把他隐藏于某个函数背后,客户随后可以调用该函数,而不是需要将转型放进自己的代码里
3 . 宁可要新型转型,也不要使用旧式转型
避免返回handle(包括引用,指针和迭代器)指向对象内部。这样可以增加封装性,也能把出现空悬指针的可能性降低
异常安全函数提供以下三个保证之一:基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据会因此而败坏,所有对象都处于一种内部前后一致的状态。然而程序的现实状态恐怕不可预料强烈保证:如果异常被抛出,程序状态不改变。调用这样的函数需要有这样的认知:如果函数成功,就是完全成功,如果函数失败,程序会恢复到“调用之前”的状态不抛掷保证:承诺绝不抛出异常,因为它们总是能够完成他们原先承诺的功能。作用于内置类型身上所有操作都提供nothrow保证,这是异常安全码中一个必不可少的关键基础材料
这三种保证是递增的关系,但是如果我们实在做不到,那么可以提供第一个基本承诺,我们在写的时候应该想如何让它具备异常安全性
1 . 首先以对象管理资源可以阻止资源泄漏
2 . 在你能实现的情况下,尽量满足以上的最高等级
inline 声明的两种方式:
1 . 隐喻的inline申请,即把定义写在class内部
2 . 明确声明,即在定义式前加上关键字inline
将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后调试和二进制升级更容易,也可使得潜在的代码膨胀问题最小化。不要只因为function templates出现在头文件,就将他们声明为inline
支持“编译依存性最小化”的思想是:相依于声明式,不要相依于定义式
1 . 头文件和实现相分离,头文件完全且仅有声明式
2 . 使用创建接口类
public继承意味着is-a的关系,即子类是父类的一种特殊化,适合基类的一定适合子类,每个派生类对象含有着父类对象的特点
在父类中的名称会被字类的名称覆盖,尤其是在public继承下,没有人希望这样的发生 为了避免被遮掩,可以使用using声明式或转交函数,交给子类
声明纯虚函数的目的就是为了让派生类只继承函数接口 声明虚函数的目的是让派生类继承该函数的接口和缺省实现 声明普通函数的目的就是让派生类强制接受自己的代码,不希望重新定义
任何情况下都不应该重新定义一个继承而来的non-virtual函数
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定的,而virtual函数——你唯一应该覆写的东西是动态绑定
区分public继承和复合 在应用领域,复合意味着一个中含有另一个,即has-a关系;在实现领域意味着根据某物实现出
当需要复合时,尽可能的使用复合,必要时才使用private: 当protected成员或virtual函数牵扯进来的时候 当空间方面的利害关系,需要尺寸最小化
多重继承时候,如果其父类又继承同一个父类,所以解决的方式就是使用virtual继承,即其父类同时以virtual继承那个父类,但是相应的也会付出一些代价,例如时间更慢,需要重新定义父类的初始化等,因此设计时最好不要让这个父类有任何数据成员当单一继承和多重继承都可以,那么最好选择单一继承,多重继承也有正当的用途,可以实现同时public继承和private继承的组合
显式接口:由函数的签名式(也就是函数名称、参数类型、返回类型)构成 隐式接口:不基于函数签名式,而是由有效表达式组成 面向对象和泛型编程都支持接口和多态,只不过一个以显式为主,一个以隐式为主 两种多态一个在运行期一个在编译期
声明模板参数的时候,class和typename是可以互换的,没什么不一样 但是标识嵌套从属类型名称的时候必须用typename 不得在基类列(继承的时候)或成员初值列(初始化列表)内以它作为基类修饰符
templete<typename T>
class Derived:public Base<T>::Nested{ //基类列表中不可以加“typename”
public:
explicit Derived(int x): Base<T>::Nested(x){//mem.init.list中不允许“typename”
typename Base<T>::Nested temp; //这个是嵌套从属类型名称
... //作为一个基类修饰符需要加上typename
}
}
模板化基类指的是当派生类的基类是一个模板
1 . 在基类函数调用之前加上 this->
2 . 使用 using 声明式 ,告诉编译器,请它假设这个函数存在
3 . 指出这个函数在基类中,使用基类::函数的形式写出来(不推荐这个,因为如果是virtual函数,则 会影响动态绑定),但是当有模板全特化的时候,确实使用的没有这个函数,那么依然会报错
模板生成多个类和多个函数,所以任何模板代码都不该和某个造成膨胀的模板参数产生相依关系 因非类型模板参数造成的代码膨胀,往往可以消除,做法是以函数参数或类成员变量替换模板参数 因类型模板参数造成的代码膨胀,往往可以降低,做法是让带有完全相同的二进制表述 的具体类型共享实现码
使用成员函数模板可以生成接收所有兼容类型的函数 如果你声明成员函数模板用来泛化拷贝构造函数和赋值操作,那么你还需要声明正常的拷贝构造函数和赋值操作
当我们编写一个模板类,它提供的和这个模板祥光的函数支持所有参数的隐式类型转换,请将哪些函数定义为模板类的内部的friend函数
当new分配失败的时候,它会先调用一个客户指定的错误处理函数(set_new_handler),一个所谓的new—handler 它是一个typedef定义出一个指针指向函数,该函数没有参数也不返回任何东西 set_new_handler的参数是个指针指向operator new 无法分配足够内存时该被调用的函数。其返回值也是个指针,指向set_new_handler 被调用前正在执行(马上就要被替换)的那个new—handler函数 一个良好设计的new—handler函数必须做以下事情:
1 . 让更多内存可被使用。此策略的一个做法是,程序一开始就分配一大块内存,而后当其第一次被调用,将它释还给程序使用
2 . 安装另一个new—handler。可以设置让其调用另一个new—handler来替换自己,用来做不同的事情,其做法是调用set_new_handler
3 . 卸载new—handler,也就是将null指针传给set_new_handler,这样new在分配不成功时抛出异常
4 . 抛出bad_alloc的异常。
5 . 不返回,调用abort或exit
6 . C++并部支持类的专属new—handler,但其实也不需要。你可以令每个类提供自己的set_new_handler和operator new即可 set_new_handler允许客户指定一个函数,在内存分配无法获得满足时调用。 Nothrow new是一个颇为局限的工具,因为它只适用于内存分配:后继的构造函数调用还是可能抛出异常
替换operator new或operator delete的三个常见理由:用来检测运用上的错误 为了收集使用上的统计数据 为了增加分配和归还的速度 为了降低缺省内存管理器带来的空间额外开销,也就是实现内存池,可以节省空间为了弥补缺省分配器中的非最佳齐位 为了将相关对象成簇集中 为了获得非传统行为了解何时可在“全局性的”或“class专属的”基础上合理替换缺省的new和delete
operator new应该内含有一个无穷的循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0 bytes申请,即将其按照1 byte分配。Class 专属版本应该处理“比正确大小更大的(错误)申请”,因为当有字类继承的时候,会出现传入的大小和父类大小不同,所以要进行判断形如if(size != sizeof(父类))operator delete应该在收到NULL指针的时候什么也不做,必要时交给全局的operator new来处理。
当你写一个placement operator new ,请确定也写了对应的placement operator delete版本。如果没有这样做,可能回发生隐微而时断时续的内存泄露当你声明placement new 和placement delete,请确定不要无意识(非故意)地遮掩正常的全局版本,你如果想提供自定义形式,请内含所有正常形式的new和delete或利用继承机制及using声明式
不同的编译器有不同的警告标准,要严肃对待编译器发出的警告信息。努力在你的编译器的最高警告级别下争取“无任何警告”的荣誉 不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失
C++标准程序库的主要机能由STL、iostream、locales组成。并包含C99标准程序库。TR1添加了智能指针(例如 tr1::shared_ptr)、一般化函数指针(tr1::function)、hash-based容器、正则表达式以及另外10个组件的支持 TR1自身知识一份规范。为了获得TR1提供的好处,你需要一份实物。一个好的实物来源是Boost。
Boost是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的C++程序库开发。Boost在C++标准化过程中扮演具有影响力的角色 Boost提供许多TR1组件实现品,以及其他许多程序库。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/EaejnQNGNB16fOyaobbGtA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。