C++ 枚举类型
枚举类型是一种可以由用户自定义数据集的数据类型。
注意:bool类型可以看成是C++语言提供的一个预定义的枚举类型。
1. 枚举类型定义
enum <枚举类型名> {<枚举值表>};
2. 初始化
枚举类型的每一个枚举值都对应一个整型数,默认情况下,第一个枚举值的值是0,然后依次增1,但也可以显示初始化任意一个枚举值对应的整形数,没定义的枚举值默认情况下在其前一个枚举值的对应整型数上加1.
留个问题:如果多个枚举值对应同一个整形数会怎样?
enum Day {Sun=7, MON=1, TUE, WED, THU, FRI, SAT}
3. 枚举变量的定义
<枚举类型> <变量表>;
或<枚举类型>{<枚举值表>} <变量表>;
4. 枚举变量的使用
1)赋值
Day d1,d2;
d1 = SUN; //true
d2 = 3; //error, 但int n = SUN;也是可以的
d2 = (Day)3;//true 但这样不安全,必须要保证该整型数属于枚举类型的值集,否则没有意义
2)比较运算
MON < TUE的结果为true,运算时将其转换为整型
3)算术运算
d2 = d1 + 1;//error,因为它d1 + 1的结果是整型
d2 = (Day)(d1 + 1);//true
4)其他
输入输出:可以输入int数,使用switch,然后复制或者输出
类下标访问:day(0)对应的是第一个枚举值sun
C++ 数组类型
数组类型是一种有固定多个同类型的元素按一定次序所构成的数据类型。
1. 一维数组
1)定义
<元素类型> <一维数组变量名>[<元素个数>];
也可以借助 typedef 类定义
typedef <元素类型> <一维数组类型名>[<元素个数>]; <一维数组类型名> <一维数组变量名>
2)操作
通过下标访问元素。
注意下标是否越界。(C++中为了保证程序的执行效率不对下标越界进行检查,越界时可以运行,但是结果不可预测)
初始化
int a[10] = {1,2,3};//其他元素初始化为0
int a[] = {1,2,3};//元素个数为3
2. 二维数组
1)定义
原理同一维数组
2)初始化
int a[2][3] = {{1,2,3},{4,5,6}}; 等同于 int a[2][3] = {1,2,3,4,5,6};//二维数组可以转成一维数组进行处理,但是要注意下标
int a[][3] = {{1,2},{3,4,5}};//第一个下标可以省略,其他的不能,更高维的数组也同此。
按行存储!
C++ 结构类型
结构类型用于表示由固定多个、类型可以不同的元素所构成的复合数据类型。
1. 结构类型定义
struct <结构类型名> {<成员表>};
或 typedef struct <结构类型名> {<成员表>}<结构体类型别名>;
1)别名可以跟结构类型名不一样,但是一般都是一样的,设置别名是为了方便像其他变量类型一样定义变量,这是保留了C的语法。
2)在结构类型定义时,对成员变量进行初始化是没有意义的,因为类型不是程序运行时刻的实体,它们不占用内存空间。
2. 结构类型变量定义
struct <结构类型名> <变量名表>;//C的用法
或 <结构类型名> <变量名表>;// C++的用法
或 struct <结构类型名> {<成员表>}<变量名表>;
3. 操作
1)访问成员:<结构类型的变量名>.<成员名>
2)对结构类型的数据可以进行整体赋值,但是要保证两者属于相同的结构(成员名和类型都相同)。
4. 存储
结构类型的变量在内存中占用一块连续的存储空间。
5. 结构类型的默认参数传递方式是值传递,因此,当结构类型很大时传输速度回受限。
6. 定义完结构类型后,其使用和平时的类型没有太大的区别,该加加该减减,不过要记住其每个成员也是一个实体。
C++联合类型
联合类型(又称共同体类型),一种能够表示多种数据(类型可以相同可以不同,变量名字不同就行)的数据类型。
1. 联合类型的定义
union <联合类型名> {<成员表>};
与结构类型类似,只是把struct 换成了 union.
在语义上,联合类型和结构类型的区别是,联合类型的所有成员占用同一块内存空间,该内存的空间大小是其最大成员的内存空间大小。
2. 操作
在程序运行的不同时刻,可以把联合类型的变量当做不同类型的变量来使用。(如果成员有三种类型,就可以当做三种类型来使用。)
联合类型的复制是按占有的整个内存空间中的内容进行复制,而不是按某个成员来赋值。
3. 联合类型除了可以实现用一种类型表示多种类型的数据外,还可以实现多个数据共享内存空间,从而节省了内存空间。所以,当一些大型的数组变量,当它们的使用分布在程序的各个阶段时(不是同时使用),就可以使用联系类型来描述。
C++ 指针类型
指针,用来描述内存地址,并通过提供指针操作来实现与内存相关的程序功能。
1. 定义
<类型>* <指针变量>;
类型决定了指向的内存空间的大小。
指针变量也是一种变量,有着自己的内存空间,该空间上存储的是另一个变量的内存空间。
可以使用typedef取别名来减少定义变量时的一些麻烦,如typedef int* Pointer;
2. 操作
1)取地址
‘&’
int* p; int x; p = &x;//p指向x的地址,p的类型是int*, &x的类型也是int*
2)间接访问
对于一般的指针变量,访问格式是:*<指针变量>
结构类型的指针变量,访问格式是:(*<指针变量>).<结构成员> 或 <指针变量>-><结构成员>
3)赋值
任何类型的指针都能赋给void *类型的指针变量,而非void * 类型的指针变量只能接受同类型的赋值。
4)指针运算
一个指针加上或减去一个整型值:<数据类型>* <指针变量>; int a; <指针变量>+a;可以理解为数组中下标的变化,
<指针变量> = <指针变量>+(a*sizeof(<数据类型>))
两个同类型的指针相减:结果是整型值,对应的是存储空间中元素的个数,可用于求数组的大小。
两个同类型的指针比较:比较的是存储内容(也就是内存地址)的大小,可用于数组求和等。
5)指针的输出
非char *类型的指针变量:cout<<p;//输出的是p存储内容(内存地址) cout<<*p;//输出的是p存储内容上的内容
char *类型的指针变量:cout<<p;//输出的是以p为起始地址的字符串,这种用法在字符串变化中很常见 cout<<*p;//输出的是p上的一个字符
3. 指向常量的指针变量
const <类型> *<指针变量>;
含义:不能改变指向的地址上的值(无论该地址上是常量还是变量),但是该变量的值是可以改变的。
4. 指针与动态变量
动态变量是在程序运行时才产生,并在程序结束前消亡。动态变量跟局部变量不同,在程序运行前编译程序就知道了局部变量的存在
创建:
new <类型名>; 如:int *p; p=new int; *p=1;
new <类型名>[<整型表达式1>]...[<整型表达式n>]; 如:int (*q)[20]; int n=5;q=new int[n][20];
void *malloc(unsigned int size); 如:double *q; int n=2; q=(double *)malloc(sizeof(double)*n);
撤销:因为动态变量不能自动消亡,需要显示撤销其内存空间。
delete <指针变量>; 如:int *p=new int; delete p;
delete []<指针变量>; 如:int *p=new int[20]; delete []p;
void free(void *p); 如:int *p=(int *)malloc(sizeof(int)*6)
应用:动态数组、链表
5. 指针 VS 无符号整数
指针从形式上看属于无符号数,但是指针可以关联到程序实体(变量或函数),指针指向某个内存地址,无符号整数的某些运算不能实施在指针上(如乘法和除法就不能)。
6. new VS malloc
1)new 自动计算所需分配的空间大小,而malloc需要显示指出。
2)new自动返回相应类型的指针,而malloc要做强制类型转换。
3)new会调用相应对象类的构造函数,而malloc不会。
对于new 和 malloc,如果程序的堆区没有足够的空间可供分配,则产生bad_alloc异常(会返回空指针NULL)。
7. delete VS free
1)delete会调用析构函数,free不会。
2)delete或free一个指针时,其实只是从编译器释放了这个地址的内存,但指针仍然指向该地址,此时的指针叫做悬浮指针,悬浮指针不为空指针,依据可以用来赋值或者和使用,所以会产生语义错误。(怎么解决呢?在delete或free后将指针设置为NULL)。
3)如果没有进行delete或free操作,就将指针指向别处,之前分配的内存空间就会一直存在但不能再被使用,也就是说造成了内存泄漏。
8. 函数指针
函数指针就是指向函数的指针。
定义格式:<返回类型> (*<指针变量>)(<形式参数表>); 如:double (*fp)(int); double fun(int x); fp=&fun;
或 typedef <返回类型> (*<函数指针类型名>)(<形式参数表>); <函数指针类型名> <指针变量>; 如:typedef double (*FP)(int); FP fp;
使用:(*<指针变量>)(<形式参数表>); 如 (*fp)(10); 相当于 fun(10);
为什么使用函数指针:可以实现多态,一个函数指针指向不同的函数就可以实现不同的功能,可以结合设计模式理解。
可以向函数传递函数,如:int func(int (*fp)(int)){};
9. 指针与数组
对数组元素的访问通常是通过下标来实现的,但是频繁地采用这种方式,有时效率不高,因为每次访问都要先通过下标计算数组元素的地址(就是上面的提到的指针变量加上一个整型数)。
10. 多级指针
指针除了可以指向一般类型的变量外,还可以指向指针类型的变量。指针变量要初始化后才能使用。
如果一个指针变量没有初始化或者赋值,访问它所指向的变量将会导致运行时刻的严重错误。
int x;
int *p;
int **q;
*p=1; //Error, p未初始化,p指向的空间不知道是什么
*q=&x; //Error,q未初始化
q=&p; //OK
**q=2; //Error, q指向的变量p未初始化
11. 指向常量的指针类型 VS 指针类型的常量
指向常量的指针类型:不能改变指向的内容。如:const int *p;
指针类型的常量:指向的地址不能发生改变,内容可以改变,但是必须要初始化。如 int x; int *const q=&x;
两者结合:const int*const r;