接口类单独头文件,实体类单独头文件,实现单独cpp文件
好处:客户程序只需要包含接口类头文件,实体类与实现只与实现细节有关。
函数有多个退出点是不好的设计
类中委托指针,只能指向抽象类。
输入参数检查,凡是,有外部输入的参数都需要检查。
成员变量以m_开头
成员函数用小写单词之间用_隔开
类的大括号跟函数一样,类名后重启一行。
类中的内容两个空格开头
英文语法问题
使用最新的编程规范
const 用来使用变量的形式定义枚举和宏
const 定义的变量内容不能改变,const变量的值在声明的赋给初值,后边不能改动。使用中const变量只能作为右值(rvalue)。在修饰形参引用变量时,表示引用所表示的变量是constant,在传参时,初始化了一个const 引用。若形参不是const,输入参数则必须是左值,输入参数的值能够被改变。
const 对象只能调用const 成员函数。
成员函数后加const 表示该成员函数不会改变成员变量。
变量的初始化(initialization)和赋值(assignment)是两个不同的概念。
变量初始化有3种方式:
int var = 8;
int var(8);
int var{8};
第一种方式是一种初始化语法,=并不是赋值操作。
它不同于以下的赋值操作。
int var;
var= 8;
对于基本数据类型来说,这两种写法能生成相同的执行码,但是对于class 变量来说,是完全不同的。
如下代码:
#include
class init_assignment
{
public:
init_assignment() {
var_name = "default";
std::cout << "init_assignment()" << std::endl;
std::cout << "var:" << var_name << std::endl;
}
init_assignment(const std::string &v) {
var_name = v;
std::cout << "init_assignment(const std::string &v)" << std::endl;
std::cout << "var:" << var_name << std::endl;
}
init_assignment(const init_assignment &input) {
var_name = input.get_var_name();
std::cout << "init_assignment(const init_assignment &input)" << std::endl;
std::cout << "var:" << var_name << std::endl;
}
const init_assignment &operator=(const init_assignment &input)
{
std::cout << "init_assignment &operator=(const init_assignment &input)"
<< std::endl;
std::cout << "before assignment var:" << var_name << std::endl;
var_name = input.get_var_name();
std::cout << "after assignment var:" << var_name << std::endl;
return *this;
}
~init_assignment(){
std::cout << "~init_assignment()" << std::endl;
std::cout << "var:" << var_name << std::endl;
}
const std::string &get_var_name(void) const {
return var_name;
}
private:
std::string var_name;
};
int main(int argc, char *argv[])
{
init_assignment test1("hello");
init_assignment test2 = init_assignment("help");
init_assignment test3;
test3 = init_assignment("rock");
}
运行结果:
~/pro/c_pp/test$ ./test
init_assignment(const std::string &v)
var:hello
init_assignment(const std::string &v)
var:help
init_assignment()
var:default
init_assignment(const std::string &v)
var:rock
init_assignment &operator=(const init_assignment &input)
before assignment var:default
after assignment var:rock
~init_assignment()
var:rock
~init_assignment()
var:rock
~init_assignment()
var:help
~init_assignment()
var:hello
可以看到在test2初始化时只调用了一次构造函数init_assignment(const std::string &v) 并没有赋值操作,所以初始化时的= 不是赋值操作符。
在进行赋值操作时,test3 = init_assignment(“rock”) 会首先构造rock, rock是一个没有名字的临时变量。调用了赋值操作符重载函数进行操作。从析构的过程中可以看到rock被析构了两次,对应的是临时变量和test3. help 被析构了一次,对应的是test2.
很多时候会出现下面的场景,函数传递一个size参数,在函数中需要动态分配一个临时数组,并且这个数组需要初始化为0。
void func(uint8_t *buffer, int size)
{
uint8_t recv_data[size];
memset(recv_data, 0, size);
}
这种方式存在明显的问题,当输入size出现异常巨大的值时,会stack overflow。
void func(uint8_t *buffer, int size)
{
recv_data = new uint8_t[](size);
}
在heap上分配内存,这时必须检测是否分配成功。这种方式比方式一略微合理,检查多一点工作量。
仍然在heap中分配内存,但是将检查动作交给STL库完成。因为STL库的分配器对内存分配失败有更加细致的处理方式。
void func(uint8_t *buffer, int size)
{
std::vector<uint8_t> data_vector(size, 0);
uint8_t *recv_data = data_vector.data();
}
头文件内使用到的类型必须有相应的头文件在这个头文件里边。