在之前已经讲过C++的基础,其实许多编程语言的入门大差不差,无外乎数据类型,数据结构,循环语句,条件语句,其实真正区别的是用途,已经语言的特性,这个才是最为重要的,不过在这之前,一个良好的代码风格是必不可少的,所以这里介绍一篇自己总结的《C++高质量代码》的笔记。
声明位于头文件和定位文件的开头(这里的定义文件可以是函数)
/*
* Copyright (c) time,方世杰
* All right reserved.
*
* 文件名称:
* 文件标识:
* 摘 要:
*
* 当前版本:1.1
* 作 者:author
* 完成日期:2022.5.20
*
* 取代版本:1.0
* 原作者 :author
* 完成日期:2022.5.20
*/
'''
* Copyright (c) time,方世杰
* All right reserved.
*
* 文件名称:
* 文件标识:
* 摘 要:
*
* 当前版本:1.1
* 作 者:author
* 完成日期:2022.5.20
*
* 取代版本:1.0
* 原作者 :author
* 完成日期:2022.5.20
'''
通过头文件来调用库功能,能够在很多场合很好的保存源代码,只需要向用户提供头文件和二进制的库即可
建议将函数的定义与声明分开,无论函数体有多小
<>头文件格式来引用标准库的头文件(编译器将从标准库目录开始搜索)
“”头文件格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)
include保存头文件
source保存定义文件,可以是多级目录
每个类声明之后,每个函数定义结束后都需要加空行。
在一个函数体内,逻辑上密切相关的语句之间不加空行,其他地方加空行分隔。
//文件命名,可以包含下划线(_)或者连字符(-),注意C++文件一般用,cc结尾,专门插入文本的文件以.inc结尾
//类名,大驼峰命名法
//命名空间命名,以小写字母命名
//普通变量,采用下划线命名规范
//成员变量,采用下划线,但是最后结尾需要额外添加_
//全局变量,前面加上g_
//静态变量,前面加上s_
//常量,以const等命名的变量,采用k+大驼峰命名法
//结构体成员变量:和普通变量相同即可
//枚举命名,和常量或宏一致,可以k+大驼峰命名 或者 全大写
//宏命名,全大写,可以接下划线,但是通常不建议使用,这一点在后面会谈到
//函数名,大驼峰命名法
//取值和设置函数命名:与成员变量命名差不多,采用下划线
这里参考谷歌的命名规范
a=b=c=0,书写整洁,可以提高编译效率
//bool
if(flag){}
//int
if(0 == a){}
//float
if(abs(a) < EPS){}
/*这里的EPS是一个误差,表示允许的误差,可以直接设置*/
//char *
if(NULL == a){}
面对多分支选择,采用switch书写效果会更好,且还可以通过break的去留,实现一些“华丽的操作”。
他会让原本结构化的设计变的糟糕,但是同样它也有适合的场所,比如他能从多重循环体重咻地一下跳到外面,用不着一层一层的使用break语句。
const相较于#define来说,多了安全检查,#define可能出现意向不到的边际效应。
这里的边际效应是指你的宏定义比如a+b没有加括号,在使用宏时,出现意想不到的错误。
不要在类声明中初始化const数据成员,需要在构造函数中进行初始化
class A
{
const int size=0;
}
// 上述声明是错误的,不能在类的声明里初始化常量,除非加上static,或者在构造函数中进行初始化
class A
{
static const int size=0;//(属于类内初始化)
}
或者
class A
{
A() {const int size=0;}//(属于构造函数中初始化)
}
class A
{
A(int size);
const int SIZE;
}
A::A(int size):SIZE(size)
这里建议不要在类的声明中去定义某些东西,最好在构造函数中进行初始化
如果想要整个类中都恒定的常量,可以采用类的枚举变量来实现,但是枚举常量只能包含整数。
但是有时候我们需要在类中定义有一定容量的数组,我们该怎么办呢,可以采用枚举
不建议省略参数的名字,建议填写完成
目的函数放在前面,源函数放在后面
多采用const,来表示仅输入的变量,可以减小代码出错的概率
多采用&的方式,可以节省临时对象的构造和析构过程,从而提高效率
参数不要太多,控制在5个以内。
为了支持链式表达式,可以附加返回值。
string中的data成员,是char类型
return不能返回“栈内存”的“指针”或者“引用”,因为该内存在函数体结束之后会被自动销毁。
return int(x+y)这种形式,可以省去不必要的构造函数和析构函数的时间,提高效率。
少使用static局部变量,你把握不住
仅仅在Debug版本起作用的宏,可以用于检查“不应该”发生的情况。这里你需要区分非法情况和错误情况之间的区别。
之后会专门开一期介绍断言
解决方法:提前检查指针是否为NULL
解决方法:一定要赋初值,哪怕是为0
问题原因:多是由于数组索引下标多1或者少1操作,特别是for循环
解决方法:申请和释放必须一一对应
- 对象调用过于复杂,难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构。
- 函数的return写错了,返回了“栈内存”的“指针”或者“引用”。
- free或者delete之后,没有将指针设置为NULL
这里说的 char *s 和char s[]
解决方法:采用指针的指针,这样我们在申请内存改变的是指针指向的指针所指向的内容发生了改变,而指针指向的指针没有改变。
野指针不是指向NULL的指针,是指向垃圾的指针。
- 没有初始化
- 释放之后,没有NULL
- 指针操作超出了变量的作用范围
int *p1 = (int*) malloc(sizeof(int) * length);
int *p2 = new int[length]
delete []objects //正确的用法
多使用指针, 越怕越要使用,可以大大的降低你的代码量
参数不同才表示函数重载(因为执行函数时,可以不带返回值,所以返回值会产生二义性)
extern “C”声明,编译是,C的编译器会将函数名改为_foo,但是C++会将函数的变量名改为_foo_int_int,所以需要进行声明。
extern "C"
{
void foo(int x, int y);
}
extern "C"
{
#include "myheader.h"
}
因为C++面对数字变量时,如果传入0.5到int类型的函数中,会自动转换为整形变量,这样就导致重装载函数出现了二义性。
这里的二义性,是指输入的参数为常量,而不是变量,变量已经被定义过类型了,所以不会产生二义性
重载特征
覆盖:指派生类函数覆盖基类函数
如果某个基类被许多个派生类继承了,想要调用某个派生类的虚函数,采用基类指针指向子类,然后运行虚函数,方便区分。
基类指针指向子类,运行同名函数时,可以各自区分,如果是虚函数,则为子类的函数
运算符 | 规则 |
---|---|
所有一元运算符 | 建议重载为成员函数 |
= () [] -> | 只能重载为成员函数 |
+= -= /= *= &= |= ~= %= >>= <<= | 建议重载为成员函数 |
所有其他运算符 | 建议重载为全局函数 |
用内联取代宏代码,可以提高函数的执行效率,本身宏代码使用是预处理器复制宏代码的方式代替函数调用,省去参数压栈、生成汇编语言的CALL调用,返回参数,执行return等过程,但是使用宏代码容易出现边际效应,他只是无脑的复制而已,没有安全检查。
内联函数比宏代码多了安全检查,建立使用内联,但是内联是以代码膨胀(复制)为代价的,仅仅省去函数调用的开销,如果执行函数体内的代码时间较短,可以采用内联函数。
非内部数据类型的成员应当采用第一种方式初始化
- 如果类B的构造函数用初始化表的方式将成员m_a初始化的效率要高
- 如果上述方法采用无参构造,那么需要两步,第一步创建m_a对象,第二步,进行赋值语句
构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构严格按照相反的次序执行。
成员初始化的顺序不受他们在初始化表中次序的影响,只有成员对象在类中声明的次序决定。是因为不同的构造函数可能有不同的初始化表,导致无法得到唯一的逆序。
if(this == &other);
判断是否进行自赋值,因为有可能这种自赋值是用过间接的形式产生的。
如果我们实在不想编写构造函数和赋值函数,又不允许别人使用编译器生成缺省函数,可以将其设置为私有函数,不用编写代码。
A &operator = (const A &a) {};
基类的构造函数、析构函数、赋值函数都不能被派生类继承。如果类之间存在继承关系,在编写上述基本函数时应注意以下事项
继承:如何类A是类B中的一种,并且B的所有功能和属性对A而言都有意义,那么A可以继承B
组合:若是在逻辑上A是B的“一部分”,则不允许B从A派生,而是通过组合得到B(正确的设计可能代码冗长,但是这是符合情理的)
const成员函数
任何不会修改数据成员的函数都应该声明为const。
时间效率是指运行速度,空间效率是指程序占用内存或者外存情况
全局效率是指在整个系统的角度上考虑的效率,局部效率是指站在模块或函数角度上考虑的效率
优化标准:
void assert(int expression);