C基本知识
.c/.cpp文件:C/C++程序的定义文件,用于保存程序的实现。
.h: C/C++的头文件,用于保存程序的声明。
头文件
- 头文件开头的版权和版本声明。
- 预处理块。
- 函数和类结构声明等。
// 版权和版本声明
#ifndef GRAPHICS_H // 防止graphics.h 被重复引用
#define GRAPHICS_H
#include <stdio.h> // 引用标准库的头文件
⋯
#include “opencv2/opencv.hpp” // 引用非标准库的头文件,从用户工作目录开始搜索
⋯
void Function1(⋯); // 全局函数声明
⋯
class Box // 类结构声明
{
⋯
};
#endif
定义文件
- 版权及版本声明
- 对头文件的引用
- 程序的实现
// 版权和版本声明
#include “graphics.h” // 引用头文件
⋯
// 全局函数的实现体
void Function1(⋯)
{
⋯
}
// 类成员函数的实现体
void Box::Draw(⋯)
{
⋯
}
代码规范
- 一行代码只做一件事情,如只定义一个变量;
- if、for等语句自占一行,执行语句不得紧随其后;
- 尽可能在定义变量的同时初始化该变量;
- 长表达式要在低优先级操作符处拆成新行,操作符放在新行之首;
- 块注释采用“/…/”,行注释 “//…”;
- 汇编使用“ ; ”注释。
//长行拆分
if ((very_longer_variable1 >= very_longer_variable12)
&& (very_longer_variable3 <= very_longer_variable14)
&& (very_longer_variable5 <= very_longer_variable16))
{
dosomething();
}
virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix,
CMatrix rightMatrix);
for (very_longer_initialization;
very_longer_condition;
very_longer_update)
{
dosomething();
}
//注释
/*
* 函数介绍:
* 输入参数:
* 输出参数:
* 返回值:
*/
void Function(float x, float y, float z)
{
…
}
if (…)
{
…
while (…)
{
…
} // end of while
…
} // end of if
表达式
优先级 | 运算符 | 结合律 |
---|---|---|
从高到低 | ( ) [ ] -> . | 从左到右 |
! ~ ++ – (类型) sizeof + - * & | 从右到左 | |
* / % | 从左到右 | |
+ - | 从左到右 | |
<< >> | 从左到右 | |
< <= > >= | 从左到右 | |
== != | 从左到右 | |
& | 从左到右 | |
^ | 从左到右 | |
I | 从左到右 | |
&& | 从左到右 | |
II | 从左到右 | |
?: | 从右到左 | |
I= += -= *= /= %= &= ^= | = <<= >>= |
零值比较
- 不可将布尔变量直接与TRUE,FALSE或1,0进行比较;
- 整形变量用“==”或“!=”直接与0比较;
- 不可将浮点变量用“==”或“!=”与任何数字比较,应设法转化为“>=”或“<=”形式;
- 指针变量用“==”或“!=”与NULL比较。
if(flag)
if(!flag) //布尔变量
if(value==0) //整形变量
if((x>=-EPSINON)&&(x<EPSINON)) //浮点型变量,EPSINON为允许的误差
if(p==NULL) //指针变量
循环
- 多重循环中,应将最长的循环放在最内层,最短的放在最外层,以减少CPU跨切循环的次数;
- 如循环体内存在逻辑判断,且循环次数很大,宜将逻辑判断放到循环体外部;
- 推荐循环控制采用半开半闭区间写法:0<=x<N(半开半闭),0<x<=N-1(闭区间);switch
switch
- case结尾不要忘了加break;
- 不要忘记最后default分支。
常量
不使用常量,程序会遇到麻烦:
- 程序的可读性差
- 程序很多地方输入同样的数字或字符串,难以保证不出错;
- 修改数字或字符串,修改地方过多。
#define MAX 100
const int MAX =100;
const float PI =3.14159
const 和define 的比较:
- const常量有数据类型,而宏常量没有数据类型。可能有边际效应。
- 有些继承化调试工具可以对const常量进行调试,但不能对宏常量进行调试。
需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。
如果某一常量与其他常量密切相关,应在定义中包含这种关系。
const float RADIUS = 100;
const float DIAMETER = RADIUS*2;
//类中的常量
class A
{⋯
const int SIZE = 100; // 错误,企图在类声明中初始化const 数据成员
int array[SIZE]; // 错误,未知的SIZE
};
class A
{⋯
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的初始化表
{
⋯
}
A a(100); // 对象 a 的SIZE 值为100
A b(200); // 对象 b 的SIZE 值为200
class A
{⋯
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];//枚举常量缺点是隐含的数据类型是整数,最大值有限
int array2[SIZE2];
函数设计
参数的书写要完整,若函数没有参数,则用void填充
void SetValue(int width, int height); // 良好的风格
void SetValue(int, int); // 不良的风格
float GetValue(void); // 良好的风格
float GetValue(); // 不良的风格
参数命名要恰当,顺序要合理
void StringCopy(char *strSource, char *strDestination); // 良好的风格
void StringCopy(char *str1, char *str2);; // 不良的风格
StringCopy(str, “Hello World”); // 参数顺序颠倒
参数如果是指针,且仅作输入用,则应在类型前加const,以防该指针在函数体内被意外修改。
void StringCopy(char *strDestination,const char *strSource);
如果输入参数以值传递的方式传递对象,则宜用“const &”方式,提高效率。
避免使用过多的参数。
返回值规则:
不要省略返回值的类型。
函数名字与返回值类型在语义上不可冲突。
值传递、指针传递、引用传递
//值传递
void swap(int a,int b)
{
int temp;
temp=a;
a=b;
b=temp;
cout<<a<<" "<<b<<endl;
}
int main(){
int x=1;
int y=2;
swap(x,y);
cout<<x<<" "<<y<<endl;
return 0;
}
//指针传递
void swap(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
cout<<*a<<" "<<*b<<endl;
}
int main(){
int x=1;
int y=2;
swap(&x,&y);
cout<<x<<" "<<y<<endl;
}
//引用传递
void swap(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
cout<<a<<" "<<b<<endl;
}
int main(){
int x=1;
int y=2;
swap(x,y);
cout<<x<<" "<<y<<endl;
return 0;
}
int x=1;
int *y=&x; //用于指针传递,y有自己独立的内存地址,存储的内容是x的地址,*y是x的值
int &z=x; //用于引用传递,可以理解为z就是x,x就是z,只不过名字不一样
内存管理
内存分配方式:
- 从静态存储区分配。如 全局变量,static变量。
- 在栈上创建。函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
- 从堆上分配,即动态内存分配。程序运行时用malloc或new申请任意内存,使用free或delete释放内存。
常见内存错误
-
内存分配未成功
在内存使用前检查指针是否位NULL。如指针p是函数的参数,在函数入口处用assert(p!=NULL)进行检查;如果用malloc或new申请内存,用if(p==NULL)或if(p!=NULL)进行防错处理。 -
内存分配成功,但未初始化就引用
-
内存分配成功且已经初始化,但操作越过了内存的边界
-
没有释放内存,导致内存泄漏
-
释放了内存却继续使用它
指针与数组
//数组
char a[]="hello";
char b[10];
strcpy(b,a); //复制不能用 b=a;
if(strcmp(b,a)==0) //不能用if(b==a)
...
//指针
int len =strlen(a);
char *p=(char*)malloc(sizeof(char)*(len+1));
strcpy(p,a);
if(strcmp(p,a)==0)
...
//数组与指针内存容量
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
//数组退化为指针
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}
const
- const只能修饰输入参数,如果输入参数采用“指针传递”,加const可以防止意外的改动指针,其保护作用;
- 对于非内部数据的输入参数,应将“值传递”改为“const 引用传递”,提高效率。如void Func(A a)改为void Func(const A &a)。
- const修饰的指针函数,返回值不能被修改,只能被赋给加const修饰的同类型指针。