一:Const
1.成员变量:声明时在前加上const关键字,注意必须在参数初始化表中初始化。
2.成员函数:在声明和定义时候在函数的尾部加上const关键字,否则编译器会认为这是重载函数。
常成员函数的作用是:如果一个成员函数不是为了直接或者间接修改数据成员,则为了明确对数据成员的保护性,定义为常成员函数。
注意:常对象只能调用常成员函数或者常成员变量
常成员函数可以被常对象或者普通对象调用,或者常成员函数,不能调用非常成员函数,因为非const函数可能修改成员数据的值。
3.常对象:在前加const,一旦确定为常对象,只能调用被const 修饰的成员。
#include "iostream" using namespace std; //class A{ //private: // int w, h; //public: // int getValue() const; // int getValue(); // A(int x, int y) // { // w = x, h = y; // } // A(){} //}; //int A::getValue() const //实现部分也带该关键字 //{ // return w*h; //???? //} //void main() //{ // //A const a(3, 4); // A c(2, 6); // //cout << a.getValue(); // cout << c.getValue() << "cctwlTest"; // system("pause"); //} #include <iostream> #include<stdlib.h> using namespace std; class A { public: A(); void C(); void B()const; //常成员函数的作用是:如果一个成员函数对类中数据成员只作访问而不作直接或间接的修改, //则最好将此函数设置为常成员函数,以明确表示它对数据成员的保护性。 private: int x; const double y; //常成员变量必须在参数初始化表中初始化 }; A::A() :y(0.0) { x = 10; //y = 0.0; } void A::C() { cout << x++ << endl; } void A::B()const { cout << x << endl; } void main(void) { const A x; A y; y.B(); //常成员函数可以被非常成员对象调用 x.B(); //x.C(); 常对象:必须进行初始化,常对象只能调用常成员函数。 system("pause"); }就近原则:const右边是类型,则值是常量;const右边是指针变量,则指针本身是常量。
const int *a;//const在*a的前面,指的是*a为常量,不能变,即a指向的内容不能变
int const *a;//同上
int *const a;//const在a的前面,a为常量,即a这个指针为常量,a不能变
const int fun(const int a) const;
第一个const修饰返回值 第二个修饰参数第三个修饰调用对象
int getValue() const; //常成员函数
int getValue();
const关键字可以用于对重载函数的区分
四种用法:
1. 修饰常量
2. 修饰函数参数 参数作为指针、引用传递时才有意义,值传递无意义
3. 修饰函数返回值
4. 修饰函数的定义体:任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性
参考: https://www.nowcoder.com/discuss/18270 http://blog.jobbole.com/108953/二、类的大小:http://blog.csdn.net/love_gaohz/article/details/7479719
1.为类的非静态成员数据的类型大小之和.
2.有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针,4个字节).
3.为了优化存取效率,进行的边缘调整.
4 与类中的构造函数,析构函数以及其他的成员函数无关.
5. 子类的大小是基类大小加上子类的大小
首先:我们要知道什么是类的实例化,所谓类的实例化就是在内存中分配一块地址.
那我们先看看一个例子:
#include<iostream.h>
class a {}; class b{}; class c:public a{ virtual void fun()=0; }; class d:public b,public c{}; int main() { cout<<"sizeof(a)"<<sizeof(a)<<endl; cout<<"sizeof(b)"<<sizeof(b)<<endl; cout<<"sizeof(c)"<<sizeof(c)<<endl; cout<<"sizeof(d)"<<sizeof(d)<<endl; return 0;}
程序执行的输出结果为:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8
为什么会出现这种结果呢?初学者肯定会很烦恼是吗?类a,b明明是空类,它的大小应该为为0,为什么 编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1.
而类c是由类a派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数的指针(vptr),在32位的系统分配给指针的大小为4个字节,所以最后得到c类的大小为4.
类d的大小更让初学者疑惑吧,类d是由类b,c派生迩来的,它的大小应该为二者之和5,为什么却是8呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,里哪个最近的倍数,就是该类的大小,所以类d的大小为8个字节.
当然在不同的编译器上得到的结果可能不同,但是这个实验告诉我们初学者,不管类是否为空类,均可被实例化(空类也可被实例化),每个被实例都有一个独一无二的地址.
我所用的编译器为vc++ 6.0.
下面我们再看一个例子.
#include<iostream.h> class a{ pivate: int data; };
class b{ private: int data; static int data1; }; int b::data1=0; void mian(){ cout<<"sizeof(a)="<<sizeof(a)<<endl; cout<<"sizeof(b)="<<sizeof(b)<<endl; }
执行结果为:
sizeof(a)=4;
sizeof(b)=4;
为什么类b多了一个数据成员,却大小和类a的大小相同呢?因为:类b的静态数据成员被编译器放在程序的一个global data members中,它是类的一个数据成员.但是它不影响类的大小,不管这个类实际产生 了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在,而类的非静态数据成员只有被实例化的时候,他们才存在.但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在.可以这么说,类的静态数据成员是一种特殊的全局变量.
所以a,b的大小相同.
下面我们看一个有构造函数,和析构函数的类的大小,它又是多大呢?
#include<iostream.h> class A{ public : A(int a){ x=a;} void f(int x){ cout<<x<<endl;} ~A(){}
private: int x; int g; }; class B{ public: private: int data; int data2; static int xs; }; int B::xs=0; void main(){ A s(10); s.f(10); cout<<"sozeof(a)"<<sizeof(A)<<endl; cout<<"sizeof(b)"<<sizeof(B)<<endl; }程序执行输出结果为:
10 ,
sizeof(a) 8
sizeof(b) 8
它们的结果均相同,可以看出类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员数据有关.
从以上的几个例子不难发现类的大小:
1.为类的非静态成员数据的类型大小之和.
2.有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).
3.为了优化存取效率,进行的边缘调整.
4 与类中的构造函数,析构函数以及其他的成员函数无关.
总结:
代码如下,这里我主要想看静态变量的内存是不是算在类中的。 1、一开始定义一个空的类,什么都没有,sizeof(A)的结果是1. 2、加入一个静态变量num,sizeof(A)的结果还是1。 3、加入一个成员变量之后,sizeof(A)的结果变成4。 4、其实如果在这里加入一个虚函数,sizeof之后会变成8,多了四个字节,因为有了虚函数,就多了一个指向vtable的指针。
三、内存字节对齐:http://blog.csdn.net/hairetz/article/details/40840881.第一个数据从0开始偏移,以后的美国一个偏移位置都是第二个元素类型的整数倍开始
2.如果包含结构体成员,需要满足结构体中元素中最大类型大小的整数倍存储
3.结构体总的大小,必须是最大成员的整数倍,不足的要补齐。
写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?讲讲字节对齐吧.
/******************************分割线
如果体系结构是不对齐的,A中的成员将会一个挨一个存储,从而sizeof(a)为11。显然对齐更浪费了空间。那么为什么要使用对齐呢? 体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。比如说读写时.............此处省略50万字
***********************************************************/
上面是你随便 google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则:(在没有#pragma pack宏的情况下,务必看完最后一行)
1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
等你看完此3条原则,2分钟已经过去,抓紧时间,实战3分钟:
typedef struct bb { int id; //[0]....[3] double weight; //[8].....[15] 原则1 float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23] 原则3 }BB;
typedef struct aa { char name[2]; //[0],[1] int id; //[4]...[7] 原则1
double score; //[8]....[15] short grade; //[16],[17] BB b; //[24]......[47] 原则2 }AA;
int main() { AA a; cout<<sizeof(a)<<" "<<sizeof(BB)<<endl; return 0; }
结果是
48 24 ok,上面的全看明白了,内存对齐基本过关.
再讲讲#pragma pack().
在代码前加一句#pragma pack(1),你会很高兴的发现,上面的代码输出为
32 16 bb是4+8+4=16,aa是2+4+8+2+16=32;
这不是理想中的没有内存对齐的世界吗.没错,#pragma pack(1),告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.
明白了不?
那#pragma pack(2)的结果又是多少呢?对不起,5分钟到了,自己去测试吧.
ps:Vc,Vs等编译器默认是#pragma pack(8),所以测试我们的规则会正常;注意gcc默认是#pragma pack(4),并且gcc只支持1,2,4对齐。套用三原则里计算的对齐值是不能大于#pragma pack指定的n值。
//#pragma pack(1) //告诉编译器,所有的对齐都按照1的整数倍对齐 bb是4+8+4=16,aa是2+4+8+2+16=32; class D //sizeof() = 40 { char c0; // 0 double d[3];// 8..31 char c; // 32.. 39 }; class S //sizeof() = 16 { int i;//0.3 short s[5];//4.13 char c;//16 }; typedef struct bb //sizeof() = 24 { int id; //[0]....[3] double weight; //[8].....[15] 原则1 float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23] 原则3 }BB; typedef struct aa //sizeof() = 48 { char name[2]; //[0],[1] int id; //[4]...[7] 原则1 double score; //[8]....[15] short grade; //[16],[17] BB b; //[24]......[47] 原则2 }AA; int main() { AA a; cout << sizeof(D) << endl; cout <<sizeof(char)<< sizeof(S) << endl; cout << sizeof(a) << " " << sizeof(BB) << endl; //48 24 system("pause"); return 0; } 四 Debug和Release assert 含义是断言,它是标准C++的cassert头文件中定义的一个宏,用来判断一个条件表达式的值是否为ture,如果不为true, 程序会终止,并且报告出错误,这样就很容易将错误定位 通常我们开发的程序有2种模式:Debug模式和Release模式 1. 在Debug模式下,编译器会记录很多调试信息,也可以加入很多测试代码,比如加入断言 assert, 方便我们程序员测试,以及出现bug时的分析解决 2. Release模式下,就没有上述那些调试信息,而且编译器也会自动优化一些代码,这样生成的程序性能是最优的,但是如果出现问题,就不方便分析测试了 五、new/delete free/malloc 基本类型的对象没有析构函数,所以回收基本类型组成的数组空间用delete和delete[]都是应该可以的; 但是对于类对象数组,只能用delete[]。 对于new的单个对象,只能用delete不能用delete[]回收空间。 参考文章:http://lib.csdn.net/article/cplusplus/238371. malloc()函数 1.1 malloc的全称是memory allocation,中文叫动态内存分配。 原型:extern void *malloc(unsigned int num_bytes); 说明:分配长度为num_bytes字节的内存块。如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。
1.2 void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。 备注:void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者...)
1.3 free void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
1.4注意事项 1)申请了内存空间后,必须检查是否分配成功。
2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
3)这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
4)虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。
1.5 malloc()到底从哪里得到了内存空间? 答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
2. new运算符
2.1 C++中,用new和delete动态创建和释放数组或单个对象。 动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。 int *pi=new int; 这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi 。
2.2 动态创建对象的初始化 动态创建的对象可以用初始化变量的方式初始化。 int *pi=new int(100); //指针pi所指向的对象初始化为100 string *ps=new string(10,'9');//*ps 为“9999999999”
如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。 也可以对动态创建的对象做值初始化: int *pi=new int( );//初始化为0 int *pi=new int;//pi 指向一个没有初始化的int string *ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化)
2.3 撤销动态创建的对象 delete表达式释放指针指向的地址空间。 delete pi ;// 释放单个对象 delete [ ]pi;//释放数组 如果指针指向的不是new分配的内存地址,则使用delete是不合法的。
2.4 在delete之后,重设指针的值 delete p; //执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。 一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;)
2.5 区分零值指针和NULL指针 零值指针,是值是0的指针,可以是任何一种指针类型,可以是通用变体类型void*也可以是char*,int*等等。 空指针,其实空指针只是一种编程概念,就如一个容器可能有空和非空两种基本状态,而在非空时可能里面存储了一个数值是0,因此空指针是人为认为的指针不提供任何地址讯息。 2.6 new分配失败时,返回什么? 1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[])以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象: class widget { ... };
widget *pw1 = new widget;// 分配失败抛出std::bad_alloc
if (pw1 == 0) ... // 这个检查一定失败
widget *pw2 = new (nothrow) widget; // 若分配失败返回0
if (pw2 == 0) ... // 这个检查可能会成功
3. malloc和new的区别
3.1 new 返回指定类型的指针,并且可以自动计算所需要大小。 比如: 1) int *p; p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int); 或: int* parr; parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
2) 而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。 int* p; p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中 double *pd=(double *) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中
3.2 malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。 除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。
4.有了malloc/free为什么还要new/delete? 1) malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
2) 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。 因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。 我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。
3) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。 如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。