C&C++编程经验总结

  1. fread以二进制的形式读取文件,函数原型如下:
    Int fread(void* buffer,size_t size,size_t count,FILE* fp);
    buffer是void型指针,指向数据块的首地址,该函数一次将size*count字节数的数据读入到buffer。
    由此,若buffer是char型指针,则size=1;若buffer是int型指针,则size=4.
  2. char a[4] = {1,2,3,4};
    Int b = *((int*)a);可以得到a[4]所在数据块以Int型表示的数值。
  3. 函数实参后自增形式下,先代入原值,函数返回后自增
  4. char的默认类型是unsigned char,这一点与其他数据类型有所区别。
  5. 匈牙利标记法:使用小写字母前缀表示变量类型如sz表示以0结尾的字符串。m_nHeight:m表示成员变量,n表示int类型;
    若n换成i,表示unsigned int类型,b表示char类型或button,pb表示char*类型,bm表示与GDI BITMAP有关的类型如m_bmHeader。
  6. #if 0…#endif 指示编译器跳过该段代码。
  7. strncpy(char* _Des,const char* _Src,Size_t count):将源地址处n个字符cpy到目标地址,若遇‘\0’结束;strcpy(char* _Des,const char* _Src);
  8. strchr(const char* s,char c):查找字符串s中字符c首次出现的位置;strrchr(const char*s,char c):查找字符串s中字符c最后一次出现的位置,该函数常用于从文件路径字符串中解析出文件名。
    因C语言函数不能重载,注意类比函数名中加字母的含义:n表示n个字符的操作,r表示反向操作。
  9. sizeof(数组名)返回该数组的总字节长度,因此sizeof(a)/sizeof(a[0])可计算数组元素个数。
  10. strstr(str1,str2)用于判断str2是否为str1的子串,返回str2在str1中首次出现的位置地址,若无则返回NULL。
  11. 调用约定:函数返回类型与函数名之间,约定了编译器对函数参数入栈顺序、堆栈清理方的处理方式。
    _cdecl:C/C++默认约定符,参数从右向左依次入栈,函数退出时由调用方清理堆栈。因每个调用方程序都包含清理堆栈代码,故可执行文件较大。
    _stdcall:winAPI专用约定符,参数从右往左依次入栈,由被调用方清理堆栈。
  12. LRESULT:函数返回值,原型为
    typedef long LRESULT
    是一个32位的整数,最高位0表示正常返回,1表示有异常,根据后面的bit可具体判断。
  13. swscanf_s及swscanf函数用法举例:swscanf_s(strTemp.GetBuffer(), _T("%dx%d"), &m_nWidth, &m_nHeight);从CString对象strTemp字符串中格式化读取帧宽和帧高变量值,可类比scanf的用法。
  14. 定义一个未知长度的字符串数组,一种写法如下:wchar_t szFilter[]=_T(“具体内容”);
  15. 类的构造函数及成员函数一般在类定义中声明,然后在同名实现文件.cpp中实现。虚函数在类外实现时不需要加virtual,所有类外实现的成员函数(包括构造函数)必须加作用域,或者using namespace。
  16. 为防止内存泄漏,一个比较好的习惯是在析构函数中统一对可能有动态内存申请的类成员作一次检查并释放内存。
  17. C++中重名的局部名字会隐藏全局名字,可以加::来调用全局的名字,例如在MFC程序中,调用WinAPI函数SetWindowPlacement前必须加::。
  18. 类成员函数的实现内部仍然属于类作用域,可以随意使用类成员。但const成员函数不能改变类成员的值。非类成员函数内部使用该类成员必须通过对象引用。在类中应使用指针定义其他类,默认构造函数将该类指针初始化为NULL,类中定义的类或结构体、数组等指针一般需要new出内存使用。
  19. SSE全称:seaming SIMD extensions,SIMD:Single Instruction Multiple Data,一组指令集用于科学计算和信号处理等。
  20. C语言算法库接口的设计原则:
    (1). 分配内存统一由用户操作,算法库只提供接口函数。
GetMemSize();Create(unsigned char* alloc_buf, void** handle);
/* 因ASCII字符为unsigned char型,故alloc_buf必须使用unsigned char*类型,表示包含字符和数据的未知字节流。 */
/* handle是主句柄,*handle存放外部分配的内存区首地址即alloc_buf。 */

(2). 用户通过接口结构体设置外部输入参数,并调用Init接口函数初始化内存对象。

Init(ParamIn* param_in, void** handle)
/* 首先应检查用户输入参数是否有误,返回错误码;
   将直接对应的参数项赋值给*handle指向的内存对象
   按映射关系对剩余的参数处理后赋给*handle指向的内存对象
*/

(3). 接口与实现分离,.h文件为用户所见。
.h 文件内容是外部输入参数结构体声明+外部接口函数声明+用户可定义宏声明
用户可定义宏对应实现中不同的代码分支,通过#ifdef…#else…#endif控制,
如#define SSE表示定义了启用SSE指令集,代码实现与未定义情形下有所不同
.c 文件内容是内存对象结构体声明+内部宏声明+内部结构体声明+接口函数实现
22. 面向对象编程的封装性:以object(物件,对象)为基本单元,class或struct是对 同一类物件的泛化。class具有属性和方法,属性可以看作是描述一类物件的某些特征,数据也是该物件的属性;方法可以理解为与物件有关的操作。
使用C语言结构体可以实现类的简单封装,在使用普通函数表示类方法时,为体现封装性,其第一项参数通常传入对应类的物件指针。
注意尽量保持各物件独立性,即单独可使用,其成员函数不需要依赖外部参数传入。
23. void** handle 看作(void*)* handle,*handle是一个void*型指针,存放32位地址,因此可以被赋值:*handle=alloc_buf,而handle是对*handle本身取地址,若*handle=alloc_buf,则handle的属性是存放unsigned char*型地址的地址,相当于&alloc_buf。若定义void* handle,*handle是void,无意义,不能被赋值,但handle是void*型,可以被赋值:handle=alloc_buf。
因此,个人认为句柄应设为void* handle。
----2019/3/7批注:当赋值操作位于函数内部,handle作为值拷贝传入时,以上赋值语句不能改变handle的值,因此正确的用法为:

void* handle; //定义
init(&handle){
	handle = alloc_buf;  //赋值
}
process(handle);  //使用
  1. 承接上一条,在面对较复杂的指针嵌套语句时,回归指针的本源——地址,指针是一个变量存储了特定位置的地址,这个变量本身也有地址。*p是取得p存储的地址处的值,&p是取得p的地址,**p是先取得p存储的地址处的值(*p),这个值*p是一个地址(指针),再取得这个地址存储的地址处的值**p,依次类推…无论嵌套多少层,p值始终是初始地址处所存储的地址,&(*p)是*p的地址,即p值;&(**p)是**p的地址,即*p值。
    多层嵌套时,*取值,&取地址,除末层外,其余各层值均为地址。
  2. 使用预先分配好的堆内存优点是数据地址固定,便于观察数据。
  3. 使用memset()时应特别注意,函数针对字节操作,byte_len=sizeof(type)*len。
  4. w+以纯文本方式读写,而wb+是以二进制方式进行读写。
    mode说明:
    w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
    w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
    wb 只写方式打开或新建一个二进制文件,只允许写数据。
    wb+ 读写方式打开或建立一个二进制文件,允许读和写。
    r 打开只读文件,该文件必须存在,否则报错。
    r+ 打开可读写的文件,该文件必须存在,否则报错。
    rb+ 读写方式打开一个二进制文件,只允许读写数据。
    a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
    a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
    ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。
    加入b 字符用来告诉函数库打开的文件为二进制文件,而非纯文字文件。
    2019/3/21补充:
    windows下r和w打开文件可能会发生读写错误,必须使用rb和wb
  5. C语言的一大弊病:函数与变量未封装在一起,导致变量的归属不明确,当函数需要频繁调用某变量作为入参时,为方便调用,该变量只能是全局的,否则,在函数的整个调用层次结构中都必须逐层引入该变量。
  6. C语言调用C++的初步理解:
    C/C++编译器的区别在于对C/C++语法的支持,C编译器不能正确解析C++独有的语法结构或关键字,因此不能编译;但C++编译器可以解析绝大多数C语言语句,为确保编译不出错,extern “C”关键字指定语句使用C编译器(C++编译器兼容C);
    若使用C调用C++类,可以将C++类封装为C结构体,如下:
struct tagApple{
	Apple a;
};

当C程序中需要实例化类时,调用函数:

struct tagApple* GetInstance(void){  //若apple有初始化参数,则传入具体arguments
	return new struct tagApple; 
}  

当C程序使用类成员函数,使用同名wrapper函数:

int GetColor(struct tagApple* pApple){
	return pApple->a.GetColor();
}

同理,释放实例:

void ReleaseInstance(struct tagApple** ppInstance){
	delete *ppInstance;
	*ppInstance = 0;  //若传入pInstance,则此处置0的是pInstance的拷贝而非
				   // pInstance本身
}

可见,C调用C++步骤如下:
(1)将C++类封装为结构体,类成员函数封装为普通函数;
(2)头文件和源文件中使用extern “C”关键词包含上述定义及实现,以便C编译器可以辨识名字;
疑问:源文件中extern “C”内包含C++语法,为何能编译通过?
(3)使用C++编译器编译具体实现wrapper的cpp文件,生成目标文件以供链接时使用;
(4)C程序include进wrapper头文件,通过调用wrapper结构体和wrapper函数的方式间接使用类,编译期间符合C语法规范,可编译通过;
(5)链接阶段使用标记-lstdc++,找到目标文件中wrapper的具体实现,替换掉符号,生成可执行程序;无论C/C++,编译后的生成二进制文件都是机器指令,因此可以正常链接。

  1. c99引入restrict关键字,用于修饰指针,表示该指针指向的数据区只能通过指针修改,不可直接修 改,编译包含该关键字的代码时,须加上编译选项:-std=c99.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值