DesignGuideOfHighQualityProgramming学习笔记

本文档提供了高质量编程的设计指导,涵盖文件结构、版式设置、命名规则、表达式、函数设计等多个方面,旨在帮助开发者编写更高效、易维护的代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*
Copyright (c) bitren haiyun
Name:Notepad_DesignGuideOfHighQualityProgramming.cpp
@author arhaiyun

*/

[1].文件结构信息
|-头文件
|-源文件
/*
*Copyright (c) 2013 bitren
*All rights reserved
*
*Filename:Notepad_DesignGuideOfHighQualityProgramming.cpp
*
*Version:0.9
*
*Author: arhaiyun
*Date: 2013-09-11
*
*Original author:***
*Date: ****-**-**
*
*/

#include<headfile.h> 引用标准库头文件
#include"headfile.h" 引用非标准库头文件,从用户目录查找

Suggestion:成员函数的定义与实现分开写,无论函数体多么的小

[2].程序的版式设置
1.适当的空行;
2.代码行每一行只做一件事情
3.注意代码行内的空格 (if、for、while 等关键字之后应留
一个空格再跟左括号‘(’,以突出关键字)
4.括号的对齐
5.比较长的代码行进行拆分
6.int* x int *x?
7.适当的注释说明
8.类的设计版式,private类型数据在前还是public函数在前?取决于你是“以数据为中心”还是以“接口函数为中心”

[3].命名规则

1.匈牙利命名法,这个也要看使用的场合
2.none or adj+none
3.windows是大小写混排currentTime; unix风格是current_time 最好不要混着用
4.取正确的容易理解的名字去表达变量或函数的意义
5.windows函数命名的时候以大写开头,类名通常以大写开头
6.常量大写+下划线 MAX_VALUE
7.静态变量前加s_ 全局变量前加g_ 类数据成员前加m_

8.openGL之类特殊情况(gl)

[4].表达式和基本语句

1.优先级不是很明确的时候添加括号
2.最好不要写过于复杂的复合表达式
3.if语句判断
|-bool flag; if(flag) || if(!flag)
|-int* flag; if(flag == NULL) or if(flag != NULL)
|-int value;  if(value == 0) or if(value != 0)
|-float value;if((value > -0.0000001) && (value < 0.0000001))
|- -不好的方式
if(condition)
return x;
return y;
-修改为
if(condition)
{
return x;
}
else
{
return y;
}
4.循环语句中将最长的循环放在最内层
5.当逻辑判断耗时较长的时候,可能会把逻辑判断移到外层
6.for语句半开半闭区间的使用 for (int i = 0; i < length; i++);
7.switch case中注意写break; 
8.谨慎使用goto

[5].常量const

1.为什么会有常量
2.const 跟 #define有何不同(类型检测等)建议用const替换#define
3.const与其它const关联的时候
4.const数据成员是在某个对象的声明周期内是常量,跟普通意义上的常量有所区分
5.不可以在类声明中初始化const数据成员,只能在构造函数初始化表中初始化
6.在类中设定常量可以通过enum{SIZE2 = 100, SIZE = 101}; int data[SIZE1]这样的方式

[6].函数设计

1.pass by value | reference | pointer
2.函数声明时候的完整性void SetValue(int value); int GetValue(void);
3.将目的参数dst放在源参数src的前面
4.参数是指针且仅作为输入时可以在前面加const修饰符 char* m_strcpy(char* dst, const char* src);
5.有时函数本身不需要返回值,但是为了设计的灵活性,可以实现链式表达式可以加返回值类型如上m_strcpy
6.☆对于赋值函数,应当采用“引用传递”方式返回class对象
7.☆对于相加函数则应当采用“值传递”的方式返回class对象
8.函数内部实现时候有效性检测assert等
9.☆return语句不可返回指向“栈内存”的指针或者“引用”,因为栈内存在函数体结束时候会自动销毁
10.return String(s + t); 
rather than 
String temp(s + t); 
return temp;
11.建议少用static局部变量
12.☆引用VS.指针
-&
int i = 6;
int j = 10;
int& k = i;
k = j;// k&i的值都会变为10
通常引用传递或者指针传递都可以实现源数据的修改,有别于值传递
实际意义上讲“引用”可以做的任何事情“指针”都能完成,注意要恰当的使用工具做恰如其分的工作

几点说明:
1.指针可以不初始化,而引用在声明的时候就必须进行初始化
2.指针可以为NULL而引用不可以
3.指针在声明周期内是可以改变的,可以指向不同的对象儿引用不行

[7].☆内存管理机制
|-静态存储区 static gloable变量 这些内存在程序编译的时候就已经分配好了内存;
|-栈内存 函数内部局部变量的存储单元是在栈内存中创建的,在函数体结束之后是会被系统自动收回的;
|-堆内存 程序员动态的通过malloc new进行分配申请,注意要通过free delete对动态申请内存进行释放。同时设定指针值为NULL防止野指针的产生
1.使用new malloc动态进行内存分配完之后要用if(p != NULL)进行放错处理
2.内存分配成功之后注意初始化
3.☆释放了内存还在使用的情况
|-调用关系复杂,分不清是否释放,这时候需要重新进行设计
|-return返回了指向占内存的指针
|-free delete之后并没有设置为NULL
4.指针参数对内存的传递
void AllocateMemory(char* data, int length)
{
data = (char*)malloc(sizeof(char) * length);
}

void Test(void)
{
char *data = NULL;
AllocateMemory(data, 100);
strcpy(data, "Hello World");//运行错误
/*
原因解析:
在AllocateMemory函数中,compiler会对每个参数制作临时副本,data对应的_data
_data = data;
对_data内存内容修改可以影响到data内存内容,这也是为什么指针可以作为输出参数原因
但上面的情况,内存的分配只是将_data指向了新的内存,但是并没有影响到data
这样看来每次执行AllocateMemory都会有一次内存泄露。
*/
}

正确的处理方法
void AllocateMemory(char** data, int length)
{
*data = (char*) malloc(sizeof(char) * length);
}
或者
char* AllocateMemory(int length)
{
char* data = (char*)malloc(sizeof(char) * length);
return data;
}
5.delete free 只是将指针所指向的内存内容释放,指针本身并不为空,为了防止野指针的出现设置为NULL
6.不同的结束方式return exit(1);
7.如果指针不为空,连续两次进行释放会导致程序运行错误
8.注意delete时候的中括号[]
Object *obj = new Obj();
delete obj;

Object *objs = new Object[10];
delete[] objs;
9.不懂指针难以写出真正高效的程序设计 love pointer love programming!

[8].C++函数的高级特性

这部分内容很多书上有详细的介绍,这里只作简单的说明
1.[overload]
|-同名函数不同参数(类型或者个数的不同)的重载,通常在同一个类中
|-全局函数与类同名函数不算作重载,因为它们作用域不一样
|-函数重载时候隐式类型转换可能导致的错误(数字本身没有类型,通常是根据函数参数类型进行隐式转换)

2.[override & virtual]
发生在有virtual的继承体系当中,函数名与参数类型个数完全一致
隐藏有几个种情况,常见的
1.父类中有virtual但是在子类中函数名相同而参数类型或者参数个数不同
2.父类中没有virtual关键字,子类中同名函数都可以隐藏父类中的同名函数
3.函数参数缺省值只在函数声明当中,而且只能从后向前逐个缺省
void function(int a = 0, int b, int c = 0);//wrong
参数缺省值可能会导致程序设计的二义性
void output(int x);
void output(int x, float = 0.0);
int x = 1;
float y = 0.5;
output(x); //error, ambiguous call

4.运算符重载
Student& Student::operator=(const Student& other)
{
//....
}
5.[inline]
inline函数代码块的直接载入,这个时候是从性能出发考虑的,要注意inline函数设计的一些规则
inline要与函数定义体放在一起,仅仅是放在声明上是不可以的

6.成员函数的声明与定义最好分开
C++ 语言中的重载、内联、缺省参数、隐式转换等机制展现了很多优点,但是这些
优点的背后都隐藏着一些隐患。正如人们的饮食,少食和暴食都不可取,应当恰到好处。
我们要辨证地看待C++的新机制,应该恰如其分地使用它们。虽然这会使我们编程时多
费一些心思,少了一些痛快,但这才是编程的艺术。

[9].Constructor, Deconstructor & Value assignment function

1.使用缺省的无参数构造函数与析构函数,是放弃了自主初始化与清除的机会,这会不会辜负了Stroustrup老人家的好意啊?
2.缺省的拷贝构造函数与赋值函数,只是进行“位拷贝”而不是“值拷贝”。如果类中含有指针变量,一定要自定拷贝构造函数与赋值函数
3.构造函数与析构函数的出现,也是在提醒我们要进行变量的初始化与内存的释放
4.使用函数的初始化表
5.☆类的const常量只能在初始化表中进行初始化,因为其不能在函数体用赋值的方式进行初始化
6.注意在初始化列表中初始化与在函数体内初始化效率的不同(调用了拷贝构造函数还是创建对象调用赋值函数)
7.拷贝构造函数与赋值函数的调用

String a("hello");
String b("world");

//调用拷贝构造函数
String c(a);

String d = b;


//调用赋值函数
String e;
e = d;

8.类(string)的赋值函数步骤 String& String::operator=(const String& other){}
|-检查自赋值 if(this == &other)
|-释放原有内存资源 delete[] m_data;
|-分配新内存资源 int length = strlen(other.m_data) + 1; m_data = new char[length]; stycpy(m_data, other.m_data);
|-返回对象的引用 return *this;

9.如果不想自己编写拷贝构造函数和赋值函数,又不想使用编译器生成的缺省函数,只需要将它们设为private
class Haiyun
{
//.....
private:
Haiyun(const Haiyun& haiyun);
Haiyun& operator =(const Haiyun& haiyun);
}

10.基类与派生类的析构函数应该为虚函数virtual

#include<iostream>
using namespace std;

class Parent
{
public:
	Parent()
	{
		cout<<"Parent constructor"<<endl;
	}

	virtual ~Parent()
	{
		cout<<"Parent destructor"<<endl;			
	}

};

class Child : public Parent
{
public:
	Child()
	{
		cout<<"Child constructor"<<endl;
	}
	
	~Child()
	{
		cout<<"Child destructor"<<endl;
	}
};

int main()
{
	Child* child = new Child();
	delete child;
	
	cout<<endl;
	
	Parent* parent = new Child();
	delete parent;

	system("pause");
	return 0;
}
output:
Parent constructor
Child constructor
Child destructor
Parent destructor

Parent constructor
Child constructor
Child destructor //如果析构函数没有virtual这行没有输出
Parent destructor

11.在写派生类的赋值函数的时候,记得给基类的数据成员重新赋值

Child& Child::operator=(const Child& other)
	{
		if(this == &other)
			return *this;
			
		Parent::operator=(other);	
		m_value = other.m_value;	
		
		return *this;
	}

12.参考文献[Cline] [Meyers] [Murry]

[10].类的继承与组合 inheritance & composition

//A example of composition
	class Eye
	{
	public:
		void Look(void);
	};

	class Mouth
	{
	public:
		void Eat(void);
	};

	class Nose
	{
	public:
		void Smell(void);
	};

	class Ear
	{
	public:
		void Hear(void);
	};

	class Head
	{
	public:
		void Look(void)
		{
			m_eye.Look();
		}
		
		void Smell(void)
		{
			m_nose.Smell();
		}
		
		void Eat(void)
		{
			m_mouth.Eat();
		}
		
		void Hear(void)
		{
			m_ear.Hear();
		}

	private:	
		Eye m_eye;
		Nose m_nose;
		Mouth m_mouth;
		Ear m_ear;
	};


[11].其它的一些建议


1.使用const提高程序的健壮性
void Operation(Object obj); //会产生临时对象(构造,析构函数等会被调用)影响效率
--> void Operation(const Object& obj);
一些其它的不修改的值或指针最好也加上const修饰符,此外还有const函数


2.const成员函数是不能修改类成员数据的,同样也不能调用类非const函数,否则会产生变异错误
3.在满足程序正确,可靠,健壮,可读的前提下尝试去提高程序执行效率
4.以提高全局效率为主,局部效率为辅
5.找到效率的瓶颈处再进行优化
6.先优化数据结构和算法,然后再优化执行代码
7.分析时间效率和空间效率,可能产生时间换空间或者空间换取时间的一些问题
8.不要追求紧凑的代码,因为这样不能产生高效的机器码


9.变量(指针,数组)被创建之后应当及时的进行初始化
10.注意数据类型转换可能引发的错误,尽量使用显式的数据类型转换
11.避免编写技巧性很高的代码
12.不要设计面面俱到,非常灵活的数据结构
13.尽量复用原有高质量的代码,重写劣质代码
14.使用标准库函数
15.如果可能的话,使用PC-Lint、LogiScope 等工具进行代码审查。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值