一、整形
按照宽度从小到大,char, short, int , long
二、内存分区
堆
栈
全局(静态)存储区
文字常量区
程序代码区
#include <iostream>
//#include <iomanip>
using namespace std;
int k = 1;
int main(){
int i = 1;
char *j;
static int m = 1;
char *n = "hello";
cout << "栈区地址为0x" << &i << endl;
j = new char(2);
cout << "堆区地址0x" << (int *)j << endl;//字符型指针会被当做字符串来处理,所以要强制转换为其他指针,void *也可以
delete(j);
cout << "堆区地址0x" <<(int *)j << endl;//和上面的是相等的,说明在释放内存的时候,指针变量中的值并没有清零或者置空,只是原地址处数据所占有的内存空间
cout << "全局变量地址0x" << &k << endl;
cout << "静态变量地址0x" << &m << endl;
cout << "文字常量区地址0x" << (int *)n << endl;
cout << "程序区地址0x" << &main << endl;
getchar();
}
三、数组
##数组初始化
- 可以使用花括号{}对数组进行初始化。
- 如果没有显式提供元素初值,元素会被像普通变量一样初始化
- 全局数组,元素会被初始化为0
- 局部数组,元素无初始化,如果只初始化部分元素,其后的元素此时也会被初始化为0
例如:
int x[4] = { 0 };
int y[4] = { 1 };
相当于x[4] = {0,0,0,0}; y[4] = {1,0,0,0};
##字符数组和字符串
char ca1[] = { 'c', '+', '+' };//不是字符串
char ca2[] = { 'c', '+', '+', '/0' };//是字符串
char ca3[] = "c++";//数组有四个元素,最后一个为'/0'
char *cp = "c++";//与上面的差别是,所占内存不同,此处只是存储了“c++”的地址,而上面的进行了复制
只有当有初始化元素的时候,才可以像上面char ca2[] = { ‘c’, ‘+’, ‘+’, ‘/0’ };这样进行声明,否则如果只是申明而没有初始化char ca2[];这样是错误的。
使用strlen(ca1)返回的值不确定,strlen 默认传入的参数是字符串是以‘/’结尾的,所以会从该地址向后直到‘/0’时返回。
3.1 二维数组
int a[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
int a[3][2] = { (1, 2), (3, 4), (5, 6) };
vector<vector<int> > vec(m, vector<int>(n, 2));
// 定义m x n的矩阵,并初始化为2;
注意两者的差别,后者相当于int a[3][2] = { 2, 4, 6 }; 注意到花括号里面是小括弧,这里是逗号运算符,取最后一个值。
###二维数组的动态声明和访问
//声明
int **a = new int*[m];
for (int i = 0; i < m; i++)
a[i] = new int[n];
//删除,释放内存
for (int i = 0; i < m; i++)
delete[] a[i];
delete[] a;//这里a和a[i]都是数组,都要采用这种方式释放内存
静态二维数组的访问:
a[i][j] = a[i*n + j];
动态数组的访问不能采用这种方式,访问方法如下:
a[i][j];
*(*(a + i) + j);
##指针数组和数组指针
指针数组
int *a[10]; //10个元素分别指向一个int型整数
数组指针
int (*p)[10];//指向有10个元素的数组的指针
int a[4][10];
p = a;
那么int (p)[4][10]指的是什么呢?p+1又跳转多少个地址呢?
没错,就是向后跳转4行10列,指向下一个4行10列
(a+1)[2]和(a[2]+1)?
一眼看上去是不是很迷茫,仔细想a+1和a是同一个类型的,实际上(a+1)[2]就等于a[3],而*(a[1]+1) = a[2][1];
int (*p)[10];
int a[10];
p = a;
int *q = a;//注意和上一行的差别;上面加一是向前移动10个元素,而这里加一向前移动一个元素;但是q、a、p中存储的地址都是一样的。
易错点
char a[] = "hello";
a[0] = 'x';//正确
a = a+2;//错误;数组首地址是一个常量,是不能修改的,数组元素是变量可以修改
char *p = "hello";
p[0] = 'x';//错误;p指向一个常量,里面的值是不能修改的,p是变量可以修改
p = p+2;//正确
四、sizeof运算符
- 是运算符,不是函数,返回类型为unsigned int
- 以字节的形式给出操作数的值所占有的内存
- 不会对其中的运算进行计算,也不会对其中的函数进行调用执行。
- 不能对struct中的位字段(位域)成员计算sizeof()值。
- 指针的sizeof(),在32bit系统下是4,在64bit系统下是8
sizeof(a++)其中的a++是不会执行的
sizeof(func())其中的func()也是不会执行的,而是会根据最终函数func()的返回类型进行计算所占有的内存大小。
注意区分strlen(),sizeof()会把‘\0’计算在内,而strlen()以‘\0’作为字符串结束的标志,并不计算在内。
##struct、union、enum所占用的内存的大小
遵循两个原则
- 整体空间是占用空间最大的成员类型所占字节数的整数倍
- 数据对齐原则-内存按结构体成员的先后顺序排列,当排到该成员变量时,其前面已经摆放的空间大小必须是该成员类型大小的整数倍,如果不够则补齐,一次向后类推
- 成员中有数组,数组是按照单个变量一个一个摆放的,不是视为一个整体
当有结构体嵌套时:
- 整体空间是外层结构体和内层结构体中占用空间最大的成员所占字节数的整数倍
- 外层结构体的成员按照先后顺序排列,当排到结构体成员时,其前面已经摆放的空间大小必须是该子结构体成员中最大类型大小的的整数倍,不够则补齐
结构体含有位域时:
- 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前面一个字段的存储,知道不能容纳为止。
在vs2010下,一个int a:4,如果后面不是位域,则占4个字节,即其类型的大小。而在Dec-C++与gcc下,无论是不是位域,所占字节数以其实际占用字节数为准,即int a:4,如果后面不是位域,仅占一个字节。 - 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍。
- 如果相邻位域字段的类型不同,不同编译器的具体实现有差异,VC6不采取压缩,而Dec-C++与gcc采用压缩。
- 如果位域字段之间穿插着非位域字段,则不进行压缩。
- 整体结构体的总大小为最快基本类型成员大小的整数倍。
使用#pragma pack()时:
可以使用#pragma pack()来改变默认的对界条件。使用方法见下程序
原则:成员的偏移量取n和成员的类型所占空间的最小值
offsetof(item) = min(n , sizeof(item));
#include <iostream>
using namespace std;
void main()
{
#pragma pack(push) //将当前pack设置压栈保存
#pragma pack(2)//必须在结构体定义之前使用
struct s1{
char c;
int i;
};
struct s3{
char c1;
s1 s;
char c2;
};
#pragma pack(pop) //恢复先前的pack设置
cout << sizeof(s1) << endl<< sizeof(s3) << endl;
getchar();
}
输出为:6,10
空结构体的大小
- 空结构体占用一个byte,用来占位;
struct s5{};
sizeof(s5) = 1;
union占用空间
- 大于等于最大成员占用的空间,因为同结构体一样要考虑对齐问题,所有存在大于的情况
union{
char b[9];
int bh[2];
}c;
占用内存大小为12byte
enum占用空间
- enum只是定义了一个常量的集合,里面没有元素,而枚举类型是当做int类型存储的,sizeof都为4;
struct dayenum{
enum day{morning, noon, afternoon}; //语句1
}DE;
sizeof(DE) = 1;
sizeof(dayenum::day) = 4;
在struct中有变量才占有空间,否则只是声明一个类型是不占有空间的。
如果将语句1改为:
enum day{morning, noon, afternoon}Dmorning;
则sizeof(DE) = 4;
注意在结构体中函数和类型重定义是不占字节的如:
class A{
int i;
int foo(){
int a;
int b;
return 0;
}
typedef char* (*f)(void*);
}
则sizeof(A) = 4;只有i所占的4个byte的内存,如果在class中存在虚函数的话(不管几个),那么将会增加4个字节的内存用来存储虚函数表指针。
五、static 和 const
在这里分两种类型进行讨论
5.1、不在类中的static和const
static
-
隐藏
加了static的全局变量和函数在除该文件外的其他文件中不再可见,失去了全局可见性,作用范围只有所在的一个文件。 -
使修饰的变量默认初始化为0
包括未初始化的全局变量(即使不使用static进行修饰也会默认初始化为0)和局部变量。要注意到全局存储区和静态存储区是同一个存储区 -
保持局部变量内容的持久性
静态局部变量的生存期是整个源程序,切具有记忆性。但是其作用域仍然与局部变量相同,只能在定义该变量的函数内使用该变量。
const
修饰普通变量
-
隐藏
使用const修饰的全局变量在除该文件外的其他文件中不再可见,这一点和static相同,但是不同的是,在const之前添加extern,就可以在其他文件中访问该const变量了 -
将一个对象转换为一个常量
使用const修饰的变量(修饰之后转换为常量),一定要在定义的时候进行初始化,并且之后是不能更改的
特别要注意在和指针以及字符串结合在一起之后不同作用
double *ptr = &value;//ptr可以改变,*ptr也可以改变
const double *ptr = &value;//ptr可以改变,*ptr不可以改变
double * const ptr = &value;//ptr不可以改变,*ptr可以改变
const double * const ptr = &value;//ptr不可以改变,*ptr也不可以改变
char * const a[2] = {"abc","def"};
a[0] = "ABC";//想要改变a[0]中存储的“abc”的在文字常量区的地址为“ABC”在文字常量区的地址,错误
a[1] = "DEF";//同上
a[0][0] = 'A';//按照上面例子中的第三行应该是可以修改的,但是要注意这里a[0][0]是在文字常量区,这里面的数据是不能修改的,所以错误
修饰函数参数和返回值
-
特别注意函数返回指针的时候
不能返回指向栈区的指针,可以返回文字常量区的指针,但是不能修改常量区的数据,这个时候可以使用const来修饰函数返回的指针类型为指向常量的指针,这样就可以避免因为修改常量区数据造成的运行时崩溃(可以在编译的时候发现) -
修改函数参数(值传递的时候const的意义不大,关键看指针或者引用传递)
当传递一个地址的时候,应该尽可能的使用const修饰,如果不这样,将会使指针参数不能引用实参(常量数据)
int fun(int *i);
const int a = 1;
fun(&a);//错误,实参a的声明是const是常量,而fun函数的形参是一个普通指针,这是允许在fun函数总改变a的值的,这是不允许的。
void f(int &i){}
f(1);//编译错误,编译器产生临时存储区,初始化为1,是常量,再产生一个地址和引用&i捆绑在一起,实参是const int类型,而形参是int类型,同上错误
5.2、在类中使用
static
- 修饰数据成员(静态数据成员)
- static数据成员独立于该类的任意对象而存在;是隶属于类的。也就是说一个对象修改了该值,那么该修改值该类的其他对象可见。
- 不能再类的声明中进行初始化,必须在类定义体的外部进行初始化,在类的定义体赋初值是错误的。
- 修饰成员函数(静态成员函数)
- 同静态数据成员一样,为整个类服务。
- 因为不属于某一个对象,因此也没有指向对象的this指针,所以静态成员函数也无法访问属于对象的非静态数据成员和非静态成员函数,只能调用静态成员函数与访问静态数据成员
- static成员函数不能声明为const。声明为const的意思是承诺不修改所属对象,而static成员函数没有所属对象
- 没有this指针的额外开销,静态成员函数会比非静态成员函数速度上快一点
const
-
const数据成员
- const数据成员必须在构造函数的成员初始化列表中进行初始化,不能在生声明的时候进行初始化,必须有构造函数,每一个类的const数据成员可以是不同的。
-
const成员函数
- const实施于成员函数的目的是为了确保,该成员函数可以作用于const对象身上。const对象,指向const对象的指针或引用只能调用其const成员函数,如果没有const成员函数,const对象将不能操作。
- const成员函数可以与相同的非const成员函数是重载的关系
class base{
void func1();
void func2();
void func2()const; //是是void func2()函数的重载,这种使用方法只能用在成员函数中,不能使用在普通函数中。
}
六、可变参数的函数
例子:printf函数
原型:
int printf(const char* format,...);
用可变参数实现多个数相加
int add(int num,...){
int sum = 0;
int index = 0;
int *p = (int*)&num+1;
for(; index < num; ++index)
sum += *p++;
return sum;
}
int main(){
int i = 1; j = 2; k = 3;
cout<<add(3, i, j, k);
return 0;
}
七、指针和引用
7.1、指针
typedef 和#define的差别
#define 标识符(参数列表) 字符串 //这里只相当于字符串替换
typedef 类型名 标识符 //相当于给类型取别名
typedef char* String_t;
#define String_d char*;
String_t a,b; //语句1
Stringd_d a,b; //语句2
上述两个定义的差别是语句1中,a,b都是指向char的指针;语句2中,a是指向char的指针,b是char变量。
特别注意函数指针的使用
- 使用函数指针调用函数
typedef bool (*cmpFcn)(cons string&,const string&);
bool lengthCompare(const string &, const string &);
cmpFcn pf = lengthCompare;
//调用方法
pf("hi","bye");
(*pf)("hi","bye");//这两种调用方法等效
- 使用函数指针做形参
void useBigger(const string&,const string&,boo(const string&,const string &));
void useBigger(const string&,const string&,boo(*)(const string&,const string &));
//等效于下面的
typedef bool (*cmpFcn)(cons string&,const string&);
void useBigger(const string&,const string&,cmpFcn);
- 返回指向函数的指针
typedef int (*pf)(int*,int);
pf ff(int);
//等效于下面的
int (*ff(int))(int*,int);
7.2、引用
-
引用其实还是指针,但是这个指针是和所引用的变量所绑定的,相当于所引用变量的别名,所有对引用的操作都是对所引用变量的操作,包括sizeof和&取地址。引用不是变量,必须在声明的时候进行初始化。注意这几点和指针的差别。
-
在类中使用时,引用成员变量,和const成员变量一样必须在构造函数中进行初始化。