C++实用经验(一)


声明:以下内容总结自《C++程序员不可不知的101条实用经验》

掌握在C++中如何使用C

要在cpp文件中使用c文件中的函数代码,要在C文件对应的头文件的函数声明前加上extern “C”。
原因:C语言编译器把函数编译成类似_Func的符号,而C++的编译器会检查函数的参数类型和作用域信息,把函数编译成类似_Z_Func_int_int的符号。extern "C"的作用就是告诉C++编译器在查找调用链接符号时采用C语言的方式,从而可以顺利调用函数。

封装、继承、多态

封装:

  1. 将对象有关的数据和行为封装为整体来处理,使得对象以外的部分不能随意存取对象内部的属性,从而有效避免外部错误对它的影响
  2. 由于只有少量外部接口对外提供访问服务,修改对象内部时,减小了内部的修改对外部的影响
  3. 不能一味强调封装,要使对象有不同程度的可见性,符合具体情况

继承

  1. 抽象机制考虑事物的共性,继承机制考虑事物的特性,两者结合才能客观描述事物的层次关系。继承机制使软件模块具有可重用性,独立性,提高开发效率,降低维护成本

多态

  1. 编译时多态:在程序编译阶段由编译系统根据参数确定与哪个同名的函数相联系,通过泛型、函数重载和运算符重载实现
  2. 运行时多态:在程序运行阶段才根据产生的信息确定需要调用哪个同名函数,通过继承和虚函数实现

计算机如何存储变量

五个存储区

  1. 只读存储区:存储常量,不允许修改,const常量,比如char *str=“abc”;
  2. 全局存储区:存储全局变量和静态变量
  3. 自由存储区:使用malloc和free函数管理的内存区域
  4. 栈区:由编译器自动分配释放,存放函数的参数值、局部变量值
  5. 堆区:由new和delete管理的内存块
  6. 堆区分配数据的风险:1)频繁分配和释放内存会造成内存空间不连续,产生大量碎片,影响程序效率 2)如果分配的内存忘记释放,会造成内存泄漏
    在这里插入图片描述

确保每个对象在使用前被初始化

生成对象时使用成员初始化列表替换赋值操作
赋值与初始化列表的区别:

  1. 赋值是在使用构造函数初始化成员后执行,初始化和赋值操作都访问了成员,造成重复访问。而直接使用初始化列表初始化后,就不用赋值操作,更高效
  2. const成员和引用成员必须用初始化列表初始化
  3. 用初始化列表初始化类中的变量时,以变量声明的顺序初始化,和成员初始化列表顺序无关,在初始化列表中初始化各个变量时,最好以声明次序为顺序

全局变量

  1. 局部变量的定义和说明可以不加区分,但全局变量的定义要在所有函数之外,且只能定义一次
  2. 要在代码文件中使用外部变量,需要声明外部变量:extern 类型 外部变量名;
  3. 外部变量的使用降低了函数独立性,尽量不用全局变量
  4. 静态全局变量只能在单个代码文件中被访问

变量定义的位置和时机

变量定义得离使用位置越近越好,尽量避免变量作用域的膨胀。尽量延后变量定义,直到非得使用变量或变量初始化的那一刻。,避免构造和析构非必须对象和执行毫无意义的默认构造行为

string getSubstr(const string &str, size_t pos){
	string substr;//变量定义过早
	if( pos > str.size() ) throw_logic_error("out of range");
	substr = str.substr(pos);
	return substr;
}
string getSubstr(const string &str, size_t pos){
	if( pos > str.size() ) throw_logic_error("out of range");
	string substr;//多余调用默认构造函数
	substr = str.substr(pos);
	return substr;
}
string getSubstr(const string &str, size_t pos){
	if( pos > str.size() ) throw_logic_error("out of range");
	string substrstr.substr(pos);//最佳方式
	return substr;
}

引用难道只是别人的替身

  1. 函数返回引用,说明该返回值能够被修改
  2. 使用引用作为函数参数,使得函数可以返回更多除了返回值外的值
  3. 不要使用const引用,因为const引用有时会伴随临时对象的产生
  4. 在函数声明时,避免使用const引用形参声明,使用非const引用形参替代

枚举和一组预处理的#define有何不同

  1. enum枚举属于常量,#define宏不是常量,只是简单替换
  2. enum类型有类型、作用域、值,#define宏没有这些性质。枚举类型主要用于限制性输入,例如某个参数只接受某种类型的有限个数值
  3. 宏没有作用域,可被重复定义和修改
  4. 整型常量尽量用枚举或const

为何struct x1{struct x1 stX};无法通过编译

struct x1{
	int data;
	struct x1 L; //结构体大小不确定,存在递归定义错误,编译不通过
	struct x1 R;
};
struct x1{
	int data;
	struct x1 *L; //结构体指针大小确定,编译通过
	struct x1 *R;
};

要正确编译程序,编译器必须知道一个结构或结构体所占的空间大小,递归定义中由于无法确定结构体大小,所以报错
递归定义的解法:
在这里插入图片描述

typedef的使用陷阱

  1. typedef定义的类型时类型的别名,不是简单的字符串替换
char *a, *b, *c;

等价于

typedef char * PTSR;
PTSR a, b, c;
  1. typedef可用于定义与机器无关的类型,保障代码的跨平台性
  2. 为复杂的声明定义一个简单的名称,简化代码,增强可读性

优化结构体中元素布局

数据对齐

访问特定变量时一般从特定的存储地址开始访问,这就要求各个类型的数据按照一定的规则在空间上排列。
数据对齐的原因:如果不按照适合平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都从偶地址开始,如果一个数据存放在偶地址开始的地方,那么一个读周期就可以读出;如果存放在奇地址开始的地方,就可能要两个读周期,并对两次读出的结果进行字节拼接,降低效率

内存对齐

  1. 内存数据对齐,能够减低数据存取的CPU时钟周期,降低内存使用量
  2. 内存对齐由编译器完成。对齐的2个规则:
    1)结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)
    2)在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍
  3. 使用#pragma pack (n)宏指定对齐值n,整个结构体的总字节长度能被n整除

既有结构体,为何要联合体

  1. 同一个联合体可以存放多种不同类型的变量,但一瞬间只能存放其中一种,这些变量共享这一段内存空间,有相同的地址
  2. 不能对联合体变量名赋值,不能在定义时初始化,不能用引用变量名得到一个值
  3. 不能把联合体变量作为函数参数和返回值,但可使用指向联合体变量的指针
  4. 结构体和联合体可以嵌套定义

提防隐式转换带来的麻烦

内置类型间的隐式转换

  1. 隐式转换时,总是由低级别到高级别转换
    double > float > long long, unsigned long long > long, unsigned long > int, unsigned int > short, unsigned short > char, unsigned char
  2. 为防止精度损失,转换时类型总是被提升为较高级别的类型
  3. 所有含有小于整型类型的算术表达式在计算之前其类型均被转换为整型

non-explicit构造函数接受一个参数的用户定义类对象直接的隐式转换

在这里插入图片描述在Func(100)中,编译器会以100为实参调用CTest的构造函数构造临时对象,然后将此临时对象传递给Func函数。要想限制这种隐式转换,就要在构造函数前加explicit。尽量减少隐式转换的使用

深刻理解void和void *

  1. 函数没有返回值,则用void
  2. 函数无参数,则应声明参数为void
  3. void *指针不能++和+1,
  4. 如果函数参数可以是任意类型的指针,,那么应声明参数为void *类型

如何判断变量是否相等

  1. 判断两个变量是否相等,两个变量的类型必须相同,不允许一个为short, 一个为int
  2. 两个不同类型的变量的比较应该禁止类型转换,重新设计
  3. 浮点数的大小比较不能通过==进行判断,必须通过差值的绝对值的精度判断
bool IsEqual(float a, float b, float absError, float relError )  
{  
        if (a==b) return true;  
        if (fabs(a-b)<absError ) return true;  
        if ( fabs(a)<fabs(b) )   return (fabs((a-b)/b)<relError ) ? true : false;  
        else return (fabs((a-b)/a)<relError ) ? true : false;  
}  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值