C++笔记1

http://blog.sina.com.cn/s/blog_51beaf0e0100fij8.html~type=v5_one&label=rela_prevarticle

 头文件
1、头文件由三部分内容组成:
(1)头文件开头处的版权和版本声明
(2)预处理块。
(3)函数和类结构声明等。

2、为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块。

3、头文件中只存放“声明”而不存放“定义”

4、不提倡使用全局变量, 尽量不要在头文件中出现象 extern int value 

这类声明

第 2 章  程序的版式
1、if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。
2、尽可能在定义变量的同时初始化该变量(就近原则)
3、关键字之后要留空格。象 const、virtual、inline、case  等关键字之后至少要留一个空格,否则无法辨析关键字。象 if、for、while 等关键字之后应留一个空格再跟左括号‘ (’ ,以突出关键字。
4、函数名之后不要留空格,紧跟左括号‘ (’ ,以与关键字区别。
5、‘ (’向后紧跟, ‘) ’ 、 ‘, ’ 、 ‘;’向前紧跟,紧跟处不留空格。
6、‘, ’之后要留空格,如 Function(x, y, z)。
7、赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=” 、 “+=”  “>=” 、 “<=” 、 “+” 、 “*” 、 “%” 、 “&&” 、 “||” 、 “<<”,“^”等二元操作符的前后应当加空格。
8、象“ [] ” 、 “.” 、 “->”这类操作符前后不加空格。
9、应当将修饰符 *  和  &  紧靠变量名
10、如果代码本来就是清楚的,则不必加注释。否则多此一举,令人厌烦。
11、边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
12、注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而
有害。
第三章 命名规则
1、标识符的长度应当符合“min-length && max-information”原则。
2、程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解
3、全局函数的名字应当使用“动词”或者“动词+名词” (动宾词组) 。类的成员函数应当只使用“动词” ,被省略掉的名词就是对象本身。
4、匈牙利命名法
类名和函数名用大写字母开头的单词组合而成。
变量和参数用小写字母开头的单词组合而成。
常量全用大写的字母,用下划线分割单词。
如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global) 。
类的数据成员加前缀 m_(表示 member) ,这样可以避免数据成员与成员函数的参数同名。
第 4 章  表达式和基本语句
1、在if语句中不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较。 应该if (flag)  // 表示 flag 为真
2、if语句中应当将整型变量用“==”或“!=”直接与 0 比较。不可模仿布尔变量的风格而写成
if (value)   // 会让人误解 value 是布尔变量
3、if中不可将浮点变量用“==”或“!=”与任何数字比较。
 假设浮点变量的名字为 x,应当将  
if (x == 0.0)  // 隐含错误的比较
转化为 
if ((x>=-EPSINON) && (x<=EPSINON))
其中 EPSINON 是允许的误差(即精度) 。
4、应当将指针变量用“==”或“!=”与 NULL 比较。 不要写成    if (p == 0)   // 容易让人误解 p 是整型变量 
5、C++/C 循环语句中,for 语句使用频率最高,while 语句其次,do 语句很少用
6、在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
7、不可在 for  循环体内修改循环变量,防止 for  循环失去控制。
8、建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。 如for (int x=0; x<N; x++) 不可for (int x=0; x<=N-1; x++)
9、每个 case 语句的结尾不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠) 。
10、不要忘记最后那个 default 分支。 即使程序真的不需要 default 处理,也应该保留语句 default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了 default 处理。
11、就象楼房着火了,来不及从楼梯一级一级往下走,可从窗口跳出火坑。所以我们主张少用、慎用 goto 语句,而不是禁用。
第 5 章  常量
如果不使用常量,直接在程序中填写数字或字符串,将会有什么麻烦?
(1)  程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意
思,用户则更加不知它们从何处来、表示什么。
(2) 在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。
(3)  如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。
1、尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。
2 、const  与 #define 的比较
 C++ 语言可以用 const 来定义常量,也可以用 #define 来定义常量。但是前者比后者有更多的优点:
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。
(2)  有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
3、需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
4、const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。
5、const 数据成员的初始化只能在类构造函数的初始化表中进行
6、怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中的枚举常量来实现
7、  枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159) 。
第 6 章  函数设计
1、参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用 void 填充。
2、如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。
3、如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
4、不要省略返回值的类型。 C 语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为 void 类型。
5、不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用 return 语句返回。
6、如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递” ,否则会出错。
7、在函数体的“入口处” ,对参数的有效性进行检查。
8、在函数体的“出口处” ,对 return 语句的正确性和效率进行检查。
(1)return 语句不可返回指向“栈内存”的“指针”或者“引用” ,因为该内存在
体结束时被自动销毁。例如
  char * Func(void)
 {
    char str[] = “hello world”; // str 的内存位于栈上
   …
   return str;   // 将导致错误
 }
(2)要搞清楚返回的究竟是“值” 、 “指针”还是“引用” 。
(3)如果函数返回值是一个对象,要考虑 return 语句的效率。例如    
   return String(s1 + s2);
这是临时对象的语法,表示“创建一个临时对象并返回它” 。不要以为它与“先创建一个局部对象 temp 并返回它的结果”是等价的,如
String temp(s1 + s2);
return temp;
实质不然,上述代码将发生三件事。首先,temp 对象被创建,同时完成初始化;然后拷贝构造函数把 temp 拷贝到保存返回值的外部存储单元中;最后,temp 在函数结束时被销毁(调用析构函数) 。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。
9、函数体的规模要小,尽量控制在 50 行代码之内。
10、尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。
11、assert 不是一个仓促拼凑起来的宏。为了不在程序的 Debug 版本和 Release 版本引
起差别,assert 不应该产生任何副作用。所以 assert 不是函数,而是宏。
12、在函数的入口处,使用断言检查参数的有效性(合法性)
13、 引用的主要功能是传递函数的参数和返回值。
14、如果的确只需要借用一下某个对象的“别名” ,那么就用“引用” ,而不要用“指针” ,以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。
第七章 内存管理
内存分配方式有三种:
(1)  从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(2)  在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)  从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
常见的内存错误及其对策如下:
  内存分配未成功,却使用了它。 编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为 NULL。如果指针 p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。 如果是用malloc或new来申请内存, 应该用if(p==NULL) 或 if(p!=NULL)进行防错处理。
内存分配虽然成功,但是尚未初始化就引用它。 论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦
内存分配成功并且已经初始化,但操作越过了内存的边界。
忘记了释放内存,造成内存泄露。 动态内存的申请与释放必须配对,程序中 malloc 与 free 的使用次数一定要相同,否则肯定有错误(new/delete 同理) 。
释放了内存却继续使用它。
有三种情况:
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了
内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
(2)函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用” ,
因为该内存在函数体结束时被自动销毁。
(3)使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针” 。 
1、用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。
2、不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
3、动态内存的申请与释放必须配对,防止内存泄漏。
4、用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针” 。
5、数组和指针的比较
(1)修改内容:
char a[] = “hello”; // 将字符串拷贝到了栈中
a[0] = ‘X’;
cout << a << endl;
char *p = “world”;     // 注意 p 指向常量字符串 , 位于静态存储区
p[0] = ‘X’;            // 编译器不能发现该错误
cout << p << endl;
(2)计算内存容量
  用运算符 sizeof 可以计算出数组的容量(字节数) 。如下所示,sizeof(a)的值是 12(注意别忘了’/0’) 。指针 p 指向 a,但是 sizeof(p)的值却是 4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于 sizeof(char*),而不是 p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
char a[] = "hello world";
  char *p  = a;
  cout<< sizeof(a) << endl; // 12 字节
  cout<< sizeof(p) << endl; // 4 字节
(3) 注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针
void Func(char a[100])
 {
    cout<< sizeof(a) << endl; // 4 字节而不是 100 字节
}
6、指针参数是如何传递内存的?
如果函数的参数是一个指针,不要指望用该指针去申请动态内存
void GetMemory(char *p, int num)
{
  p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
  char *str = NULL;
  GetMemory(str, 100);  // str 仍然为 NULL 
 strcpy(str, "hello"); // 运行错误
}
毛病出在函数 GetMemory 中。编译器总是要为函数的每个参数制作临时副本,指针参数 p 的副本是 _p,编译器使  _p = p。如果函数体内的程序修改了_p 的内容,就导致参数 p 的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p 申请了新的内存,只是把_p 所指的内存地址改变了,但是 p 丝毫未变。所以函数 GetMemory并不能输出任何东西。事实上,每执行一次 GetMemory 就会泄露一块内存,因为没有用free 释放内存。
如果非得要用指针参数去申请内存, 那么应该改用 “指向指针的指针
 
void GetMemory2(char **p, int num)
{
  *p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
  char *str = NULL;
 GetMemory2(&str, 100);  // 注意参数是 &str,而不是 str
 strcpy(str, "hello"); 
  cout<< str << endl;
 free(str); 
}
由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单
 
char *GetMemory3(int num)
{
  char *p = (char *)malloc(sizeof(char) * num);
 return p;
}
void Test3(void)
{
  char *str = NULL;
  str = GetMemory3(100);  
 strcpy(str, "hello");
  cout<< str << endl;
 free(str); 
}
7、 别看 free 和 delete 的名字恶狠狠的(尤其是 delete) ,它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
8、有了 malloc/free 为什么还要 new/delete  ?
对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free。
  因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数。

如果用 free 释放“new 创建的动态对象” ,那么该对象因无法执行析构函数而可能导致程序出错。 如果用 delete 释放“malloc 申请的动态内存” ,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete 必须配对使用,malloc/free 也一样。

如果用 new 创建对象数组,那么只能使用对象的无参数构造函数。例如
  Obj  *objects = new Obj[100]; // 创建 100 个动态对象
不能写成
  Obj  *objects = new Obj[100](1);// 创建 100 个动态对象的同时赋初值 1
在用 delete 释放对象数组时,留意不要丢了符号‘[]’ 。例如
 delete []objects; // 正确的用法
delete objects;  // 错误的用法
后者相当于 delete objects[0],漏掉了另外 99 个对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值