C++变量和类型实用经验

文章内容均抄自    刘光《C++程序员不可不知的101条实用经验

1.计算机是如何存储变量的
只读数据区:存储常量和恒指
全局/静态存储区:全局/静态存储区主要存储全局变量和静态变量。在C语言中,全局/静态变量又分为初始化的全局/静态变量和未初始化的全局/静态变量,初始化后的全局/静态变量存储在data去,未初始化后的全局/静态变量存储在bss区。而在C++中没有这个区分。
自由存储区:指CRT(C运行时库)通过malloc,free函数管理的内存。在部分编译器上,自由存储区和堆两块内存都是同一种管理方式,课统称为堆区。
如    int *p = static_cast<int *>(malloc(sizeof(int)));
栈区:栈区中存储的数据有由编译器自动分配释放,主要存放函数的参数值、局部变量值等。栈区在分配数据时,地址自动对低地址增长。程序执行过程中,栈可以动态地扩展和收缩。
堆区:由new/malloc分配的内存块,编译器不负责他们的释放
             注意:堆区上分配数据有两个风险①频繁分配和释放内存会造成内存空间的不连续,从而造成大量的内存碎片,使程序效率降低。②分配的数据如果没有释放,很容易造成内存泄漏。

2.确保每个对象在使用前已被初始化
C++规定对象中成员变量的初始化发生在对象的构造函数之前
①成员变量的 赋值
CStudent::CStudent(int a,int b)
{
        m_a = a;
        m_b = b;
}
②成员变量的 初始化
CStudent::CStudent(int a,int b):m_a(a),m_b(b)
{
}
赋值和列表初始化的区别:
(1)使用赋值初始化对象变量时,在构造函数执行前会调用默认构造函数初始化m_a,m_b,然后在执行赋值操作。这样默认构造函数所做的一切都白费了。通过初始化列表的方法
避免了重复操作,因此效率更高。
(2)如果成员变量是const或reference类型,它们就一定要用初始化列表初始化
(3)基类先于子类初始化,class中的变量总是以变量声明的顺序初始化,和成员初始化列表顺序无关,因此在成员初始化列表中初始化各变量时,最好以声明次序为顺序。
简单的单例(singleton)模式
class CStudent
{
        public:
                static CStudent * INstance();
                char * GetName();
};

static CStudent * Instance()
{
        static CStudent student;
        reutrn &student; 
}
CStudent * Xiaoming = CStudent::Instance();
char * name = Xiaoming->GetName();
为避免在对象初始化前过早地使用它们,①手动初始化内置类型对象②利用成员初始化列表初始化对象的所有成员③在初始化次序不确定的情况下,加强设计

3.局部变量和全局变量的区别
小心陷阱:①全局变量和静态变量如果没有手动初始化,则由编译器初始化为0 ②局部变量是编译器永远不会初始化的变量。如果没有手动初始化,局部的值是随机的
请谨记:
(1)若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度
(2)若全局变量仅由单个函数访问,则可以将这个变量改为函数的静态局部变量 ,以降低模块间的耦合度
(3)函数中必须使用static变量的情况,如某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值

4.掌握变量定义的位置和时机
简易变量定义得离使用位置越近越好
变量定义在循环体内或外的区别:
①变量定义在循环体外
CStudent a;
for(int i=0;i<n;i++)
{
        a=...;
}
②变量定义在循环体内
for(int i=0;i<n;i++)
{
        CStudent  a;
        a.GetName();
}
方法①:1个构造 + 1个赋值 + 1个析构
方法②:n个构造 + n个析构
当n很大时,如果赋值操作代价较高,则方法②好,如果构造和析构代价高则方法①好

5.引用难道只是别人的替身
注意:①声明引用的引用、指向引用的指针,指向引用的数组都是非法的②用关键字const和volatile修饰引用会造成编译错误,就算没有编译错误,但编译器也会默认忽略这些修饰
常量不能给普通引用初始化,但可以给const引用初始化 
如:     int &a = 12;            //错误
            const int &a = 12;  //正常编译通过
请谨记:
(1) 若非必要不要使用const引用,因为const引用有时会伴随着临时对象的产生
(2)函数声明时,请尽量避免const引用形参声明,使用非const引用形参替代,以防因返回const引用生成的临时变量而导致程序执行错误

6.枚举和一组预处理的#define有何不同
请谨记:
(1)枚举用于某些限制性输入环境,可限制某个参数接收有限个数组,而#define定义的系列宏无法实现这些功能
(2)#define宏可以重复定义导致宏值可被修改,所以整型变量的宏尽量用枚举或const替代

7.为何struct a{struct a aa};无法编译通过
定义一个数据类型,而此数据类型又包含此数据类型的变量。通常做法应该是把数据类型中的变量定义为指针形式
struct Node
{
        int data;
        struct Node *LeftNode;
        struct Node *RightNode;
};
若将 struct Node *LeftNode;改成struct Node LeftNode;则会编译错误,原因是C/C++采用静态编译模型,程序运行时结构大小会在编译后确定。程序要正确编译,必须要知道一个结构所占用的空间大小。因为指针类型是4字节长度,而结构体Node此时还未知,所以编译错误。
还有一种错误实例是类递归定义。CA类包含CB类的实例,而CB类包含CA类的实例,解决方法有
①前向声明实现,定义CB类时在前面声明CA类
②friend声明实现,在CB类中定义CA类实例前将CA类声明为友元类

8.实现可变数组
struct NameStr
{
        int namelen;
        char namestr[];
};
//创建一个动态数组,数组长度为n
struct NameStr * p_NameStr = malloc(sizeof(struct NameStr) + (n)*sizeof(char));
小心陷阱:
(1)struct hack动态数组必须分配于堆上。它通过struct数据结构实现, 动态数组成员(如namestr)必须是struct的最后一个数据成员
(2)struct hack存在一个缺点,不需要编程人员手动分配和释放

8.typedef使用的陷阱
宏和typedef的区别:
(1)宏定义知识简单的字符串替换
(2)typedef定义的类型是类型的别名,typedef后面是一个整体声明,具有一定的封装性,不是简单的字符串替换
小心陷阱:
(1)typedef主要为复杂的声明定义简单的别名,它是一种存储类的关键字,与auto,extern,mutable,static,register等关键字不能出现在同一个表达式中

9.优化结构体中元素的布
定义结构体时数据成员的顺序决定了结构体占用的内存,优化原则: 把结构体中的变量按照类型大小从小到大顺序声明,尽量减少中间的空闲填充字节

10.既有结构,为何引入联合
结构体和联合体的区别:
(1)结构体每个成员都有自己的内存,而联合体各个成员共享一段内存空间
(2)一个结构体变量的总长度等个各个成员长度按字节对齐后的总和,而联合体变量的长度等于成员中长度最大的那个成员的长度
联合体特点:
(1)同一联合体可用来存放几种不同的数据成员,但是在每一瞬间只有一个成员起作用,其他的成员不起作用
(2)联合体中起作用的成员是最后一次存放的成员,在存于一个新成员后,原有成员就会失去作用。
(3)联合体变量的地址和其各成员的地址都是同一地址
(4)不能对联合体变量名赋值,也不能企图引用变量名来得到一个值,也不能在定义联合体变量时对它进行初始化
(5)不能把联合体变量作为函数参数,也不能作为函数的返回值,但可以指向联合体变量的指针
(6)联合体类型可出现在结构体类型的定义中,也可以定义联合体数组;反之结构体也可以出现在联合体类型的定义中,数组也可以作为共用体的成员

11.提防隐式转换带来的麻烦
①内置类型间的隐式转换:double > folat > (unsigned)long long > (unsigned)long > (unsigned)int > (unsigned)short > (unsigned)char
②使用explicit限制的构造函数
class CStudent
{
        public:
                CStudent(int a);
                CStudent(congst char * b,int c = 0);
};
CStudent s1 = 10;            //编译通过
Cstudent s2 = "hello";      //编译通过
为了控制这种隐式转换,C++引入了explicit关键字,在 构造函数声明时添加explicit关键字可禁止此类隐式转换

12.深刻理解void和void*
void字面是“无类型”,void*则为“无类型指针”。 void*可以指向任何类型的数据
使用注意:
(1)如果函数没有返回值,应声明为void类型(因为不指定返回类型,C++会默认返回类型为int)
(2)如果函数无参数,那么应声明其参数为void
(3)小心使用void指针类型,不能对void指针进行算法操作
(4)如果函数的参数可以是任意类型的指针,那么应声明其参数为void*
(5)void不能代表一个真实的变量

13.如何判定变量是否相等
小心陷阱:
(1)判断两个变量是否相等时,两个变量的类型必须相同
(2)若两个变量类型不相同,不仅应该禁止隐式转换,显式转换更应该被命令禁止
(3)不能简单的用“==”去判断两个浮点型变量是否相等,一般采用绝对精度和相对精度相结合的方法





















































猜你喜欢

转载自blog.csdn.net/sinat_39061823/article/details/80569064