1、gcc常用选项
-o输出文件名:指定生成的文件名
-E:只做预编译
-c:只做编译,不做链接
-S:产生汇编文件
-l:指定链接库
-L:指定库的目录
-I:指定头文件的目录
-std=c89/c99:指定语言标准
-Wall:生成警告
2、#include指令
用<>包含的头文件,到标准头文件路径(usr/include)下寻找。
用""包含的头文件,优先到当前目录下寻找。
一般情况下,建议用<>包含标准头文件,用""包含自定义头文件。如果头文件既不在当前目录下,也不在标准头文件路径中,可以通过-I选项告诉编译器到哪里寻找这样的头文件。
3、变量类型包括char(1字节)、int(4字节)、short(2字节)、long(4字节)、long long(8字节)、float(4字节)、double(8字节)。
4、变量的定义
类型变量名 = 初值;
inta = 10;
intb; // 未初始化的变量,其值不确定
5、printf函数和scanf函数
printf(格式化串, 变量表);
scanf(格式化串, &地址表);
%d:十进制 %s字符串 %u 无符号int型 %f double类型 %s字符串 %c 单个字符 %p 地址
6、C语言的基本数据类型包括:
char:字符/单字节整数,1字节
unsignedchar:非负的单字节整数,1字节
short:双字节整数,2字节
unsignedshort:非负的双字节整数,2字节
int:四字节整数,4字节
unsignedint:非负的四字节整数,4字节
long:四字节整数,4字节
unsignedlong:非负的四字节整数,4字节
longlong:八字节整数,8字节
unsignedlong long:非负的八字节整数,8字节
float:浮点数,4字节
double:双精度浮点数,8字节
longdouble:长双精度浮点数,12字节.
一次调用输入多个数据,各个数据以空白字符(空格、制表、回车)分隔。
7、操作符:sizeof ():获取一个变量或类型的字节数。
8、前后缀自增减的区别:
前缀:先自增减,再计算表达式。
后缀:先计算表达式,再自增减。
a++表达式和++a表达式的值都是从操作数a的内存空间中得到的。二者的区别在于,一个是先取表达式的值再自增,另一个是先自增在取表达式的值。
9、逻辑运算
1)逻辑与(&&)、逻辑或(||)、逻辑非(!)
A&& B:只有当A和B的值都为真(非零)时,表达式的值才为真。
A|| B:只有当A和B的值都为假(零)时,表达式的值才为假。
!A:当A的值为真时,表达式的值为假,当A为假时,表达式的值为真。
2)短路运算
A&& B:如果A的值为假,则B不处理。
A|| B:如果A的值为真,则B不处理。
10.位运算
1)位与(&)/位或(|)/位异或(^)/位反(~)
位与:参与运算的两位都是1结果为1,否则为0。位与可以置某一位为0,也可以得到某一位的值。
10110001 A
11101111 B
&)-----------
10100001 C
^
10110001 A
00010000 B
&)-----------若A&B==B,则A中此位为1
00010000 C
^
位或:参与运算的两位都是0结果为0,否则为1。位或可以置某一位为1,也可以得到某一位的值。
10100001 A
00010000 B
|)------------
10110001 C
^
10100001 A
11101111 B
|)------------若A|B==B,则A中此位为0
11101111
^
异或:参与运算的两位相异为1,相同为0。异或可以翻转某一位,想翻哪位哪位就是1,其余为0。
10101010 A
00100100 B
^)------------
10001110 C
^ ^
A^B->C
C^B->A
C^A->B
异或还可用于在不增加第三变量的前提下,交换两个变量的值。
inta = 10, b = 20;
intc = a;
a= b;
b= c;
--------------------
a: 1011
b: 0110
a= a ^ b; // a : 1101
b= a ^ b; // b : 1011
a= a ^ b; // a : 0110
位反:按位取反
01100001
~)-----------
10011110
2)左右移位
A.有符号数:左移补零,右移补符
10101010<< 1 -> 01010100
-86 84
10101010>> 1 -> 11010101
-86 -43
B.无符号数:左移右移全补零
10101010<< 1 -> 01010100
170 84
10101010>> 1 -> 01010101
170 85
在不发生溢出的情况下,左移一位即乘2,右移一位即除2。
00000001<< 1 -> 00000010
1 2
11、取地址和解引用(取目标)运算
取地址&:获取一个变量的地址,即其首字节的地址。
解引用*:根据地址获得变量的值。
12、类型转换
1)类型升级:由低级类型转换为高级类型。浮点高于整型,长整型高于短整型(long long >long > int > short > char)。若有符号类型能够表示无符号类型的值,则取有符号类型,否则按无符号处理。
2)强制转换:目标类型变量 = (目标类型)源类型变量。
doublef = 1.23;
intn = (int)f; // n : 1
无论哪种形式的类型转换对于源变量都不会产生任何影响。
13、条件运算
条件表达式 ? 表达式1 : 表达式2
若条件表达式为真,则整个表达式的值取表达式1的值,否则整个表达式的值取表达式2的值。
14、运算符的优先级和结合序
按优先级从高到低排列:
初等运算:()、[]、.、->
单目运算:!、~、++、--、类型转换、&(取地址)、*(解引用)、sizeof
算数运算:*、/、%高于+、-
移位运算:>>、<<
关系运算:>、>=、<、<=高于==、!=
位与运算:&
异或运算:^
位或运算:|
逻辑运算:&&高于||
条件运算:?:
赋值运算:=、复合赋值
逗号运算:,
多数运算符具有左结合性,单目/三目/赋值运算符具有右结合性。
左结合:
a- b + c <==> (a - b) + c
右结合:
-----a<==> -(--(--a)) // g++
a> b ? a : c > d ? c : d <==> a > b ? a : (c > d ? c : d)
a= b = c <==> a = (b = c)
括号不怕冗余。
15、结构化程序设计,就是以顺序、分支、循环三种基本控制结构构建任意复杂的单入口单出口的程序。
(1)条件分支
1)语法形式
if(表达式1) {
当表达式1为真(值非零)时执行的语句;
}
elseif (表达式2) {
当表达式2为真(值非零)时执行的语句;
}
...
else{
当表达式1-N都不为真时执行的语句;
}
2).if只能出现1次,else if可以出现0-N次,else可以出现0-1次。
3).if-else结构应用于需要根据不同的条件执行不同代码的场合。
4).if-else结构最多只能执行1个语句块。若有else分支,则必选其一执行,若无else分支,则可选其一执行。
5).如果{}中只有一条语句,或者一个独立的控制结构,那么可以省略该{}。
6).else和else if总是和最近的if配对。
(2)、开关分支
1).语法形式
switch (控制表达式) {
case 常量表达式1:
语句块1;
break;
case 常量表达式2:
语句块2;
break;
...
default:
2).控制表达式被当做整数处理,可以是字符,但是不能是浮点数和字符串。常量表达式必须是常量,如:3、'A'、2+5。不允许有重复的分支。
3).default不一定在最后,但是如果default出现在其它case之前,其最后的break不能省略。
4).一般而言所有能够使用switch-case结构的场合都可以用if-else替代,反之不行。
(3)、循环
1.while循环
while(循环控制表达式) {
循环体语句块;
}
S1:计算循环控制表达式,若为真则执行循环体语句块,否则退出循环;
S2:执行循环体语句块后,执行S1。
1)如果循环控制表达式恒为真,则构成无限循环;
2)while循环的循环体可能一次都不执行。
2.do-while循环
do{
循环体语句块;
} while (循环控制表达式); // 分号一定要有
S1:执行循环体语句块;
S2:计算循环控制表达式,若为真则执行S1,否则退出循环。
Do-while循环的循环体至少会被执行一次。
3.for循环
for(表达式1; 表达式2; 表达式3) {
循环体语句块;
}
S1:计算表达式1;
S2:计算表达式2,若为真则执行循环体语句块,否则退出循环;
S3:计算表达式3,执行S2。
continue语句:中断本次循环,继续下一次循环。对于while/do-while循环,continue -> 计算循环控制表达式 -> ...,对于for循环,continue -> 计算表达式3 -> 计算表达式2 ->空语句:仅包含一个分号的语句。
16、数组是用来存储多个类型相同的数据的数据结构——容器。
多个,类型相同。数组是一段连续的内存区域。数组变量的本质就是其首字节的地址。数组是数据的容器,而非数据本身。数组中的每个数据项被称为数组的元素,一个数组包括一到多个元素。数组中元素的个数被称为数组的长度。数组下标就是元素在数组中的索引号,从零开始。数组元素通过数组名和下标的组合进行访问,[]叫做下标运算符:数组名[下标],表示数组中第“下标”个元素。
17、元素类型数组名[长度(元素个数)] = {初始化表};
18、数组的初始化
intarr[10] = {100,200,300,400,500,600,700,800,900,1000};
依次取初始化表中的值对数组中的每个元素进行初始化。
intarr[10] = {100, 200, 300};
初始化没有显示指明初始值的元素,一律初始化为零。
intarr[10] = {0}; // 全部初始化为0
intarr[10] = {}; // 全部初始化为0
intarr[10]; // 不做初始化
如果在定义数组时不指定长度,则取初始化表的长度作为数组的长度。
intarr[] = {1,2,3,4,5}; // 等价于int arr[6] = {1,2,3,4,5}
intarr[]; // 错误!
计算数组长度:sizeof (arr) / sizeof (arr[0])
19、1)键盘->键盘缓冲区->程序输入缓冲区->程序变量。对于scanf("%d", &i),如果输入的数据不是int而是字符,scanf读不走这些字符,该字符会一直留在程序输入缓冲区中,进而影响后续scanf的读取操作。解决方案:
scanf("%*[^\n]"); // 忽略(丢弃)缓冲区中除\n以外的全部字符
scanf("%*c"); // 忽略(丢弃)\n字符
经过这两步,使程序输入缓冲区处于干净的状态,后续的scanf读取可以正常工作。
20、arr[i][j]代表arr的第i个元素的第j个元素。N维数组:由多个N-1维数组组成的数组。N维数组的元素是N-1维数组。一维数组元素的类型二维数组名[二维数组长度][一维数组长度];
21、函数就是一系列语句的组合,用以实现一些相对独立且具有一定通用性的功能。
22、函数的定义语法
返回类型函数名 (形参表) {
函数体语句;
}
intmain (void) {
// ...
return 0;
}
23、声明语法:返回类型函数名 (形参表);
24、 函数的调用语法:
函数名(实参表);
(1)有参数的函数在定义时使用的参数叫形参,当调用该函数时需要传入的参数叫实参。将实参传递给形参的过程,相当于将实参的值赋值给形参变量。从内存的角度看,实参属于调用者的栈区,形参属于被调用者的栈区。
(2)当以数组名作为实参调用函数时,实际传给形参的是该实参数组的首地址(而非实参数组的副本),因此在函数中对形参数组的修改也就是对实参数组的修改。
在函数中对形参数组不用通过sizeof(arr)/sizeof(arr[0])获得该数组的长度。如果函数有必要了解数组中有效数据的个数,应该通过另一个附加参数传入该函数。
(3)return可以用于退出函数,exit()用于退出整个程序,只有在main函数中。
25、递归就是函数自己调用自己。使用递归的时机:需要进行的操作就是正在进行的操作。
注意:递归一定要有终止条件,否则就会形成无限递归。递推就是一种迭代算法。
26、递归函数可以简化算法,但是效率不高。如果不是十分必要的话,尽可能避免使用递归。
27、指针的用法
1)将指针作为函数的参数,传递变量的地址,进而在多个函数中访问相同的内存数据。
2)指针也可以作为函数的返回值,但是不要返回指向局部变量的指针。因为函数返回以后,其局部变量所占用的内存将随函数栈一起被释放,所得到的指针为野指针。可以返回全局变量、静态局部变量、实参变量以及成员变量的指针。因为这些变量在函数返回以后依然有效。
28、指针计算
1)一个指针型数据可以和一个整数做加减法计算,表示地址计算。所得结果与指针的类型有关。
int型指针+1:地址+4
short型指针+1:地址+2
char型指针+1:地址+1
...
X型指针+/-N:地址+/-N*sizeof(X)
2)通过指针计算可以访问数组中的元素。所谓的下标运算“[]”,其本质就是指针计算。
3)相同类型的指针之间可以做减法运算,所得为两个指针之间的距离,以指针类型为单位。
29、数组和指针
1)数组名就是数组的首地址,即首元素的地址,其本质就是一个指针,但其类型为常量。
2)以数组为参数的函数,所传递的实参是数组的首地址,而形参就是数组元素类型的指针。
30、常量、常量指针和指针常量
1)const可以修饰普通变量,该变量就会被当做常量来处理,即其值一经初始化就再不能被修改。
2)常量指针:指向常量的指针,即无法通过该指针修改其目标。
constint* p;
intconst* p;
3)指针常量:指针型的常量,即无法修改其值的指针。
int*const p;
constint* const p; // 常量指针常量,p本身不能改,其目标亦不能改。
31、字符指针数组
数组首地址:数组首元素的地址,数组名。
指针数组:数组中的每个元素都是指针。
32、#define
1)常量宏
提高代码的可读性,可维护性。
#define宏名宏值
2)参数宏(宏函数)
#define宏名(宏参数) 宏值
预处理器将宏参数替换为实际值后,代码中的宏名全部替换为宏值。
注意:小括号不要少。参数和被替换的宏值。
3)语言扩展
4)#和##
#:表示将其后的宏参数作为字符串字面值进行替换。
##:表示将其后的宏参数替换以后与其前面的部分粘连在一起。
5)预定义宏
A.标准预定义宏
__FILE__:当前文件
__LINE__:当前行
__DATE__:编译日期
__TIME__:编译时间
__STDC__:是否支持标准C
B.编译器预定义宏
GNU编译器:__GNUC__
微软编译器:_MSC_VER
C.用户预定义宏
通过gcc的-D选项设置预定义宏。
如果宏是一个字符串的字面值,那么-D后面的宏值需要用\"引起来。
33、条件编译指令
#if// 如果,#if VER==1
#ifdef// 如果定义了...
#ifndef// 如果没有定义...
#elif// 否则如果...
#else// 否则
#endif// 和#if/#ifdef/#ifndef配对使用
#undef// 取消定义,和#define相反
满足条件(条件表达式的值非零)的代码参加编译,否则不参加编译。
34、构建脚本
makefile三要素:
目标: 依赖
<制表符>规则,从依赖获得目标的方法。
35、结构体
在C语言中可以使用结构体定义用户自己的数据类型,类似于数组,但是结构体中的成员可以是不同的数据类型。
1)直接定义结构体形式的变量
struct{
若干成员;
} 变量名;
2)先定义结构体类型,然后用该类型定义变量。
struct结构体类型名 {
若干成员;
};
struct结构体类型名变量名;
3)先用typedef定义结构体类型别名,再用该别名定义变量
typedef原始类型类型别名;
4)嵌套结构:一个结构体类型中的某个成员也是结构体类型的。
结构型变量及数组的初始化
通过{}对结构型变量进行初始化s。
访问结构体的成员
1)通过变量访问成员:用.运算符,亦称成员访问运算符。
2)通过指针访问成员:用->运算符,亦称间接成员访问运算符。
结构类型的变量可以作为函数的参数,但是与基本类型参数的情况一样,虚实结合的过程只是值的复制,因此在函数内部对形参所做的修改,不会影响实参。如果希望函数能够改变实参的值,应该传入变量的地址。
即使是以读方式访问形参的函数,仅仅出于性能的考虑,也可以以地址方式传参,避免结构复制所带来的开销。为了防止在函数中意外地修改实参,可以用常量指针定义参数。
36、内存对齐与补齐
1)对齐:结构中的每个成员在内存中的起始位置必须是其自身字节长度的整数倍,超过4字节的按4字节算。
2)补齐:结构体的字节长度必须是其字节数最多的成员的字节长度的整数倍,超过4字节的按4字节算。
37、共用体(联合)
1)联合与结构的定义语法一致,只是其关键为union而不是struct。
2)联合中的成员在内存是共享的。
3)联合的字节长度就是其字节数最大的成员的字节长度。
38、枚举
1).枚举是一个整型常量的列表,一般而言,其值为有限个,每个值都是枚举常量(整型)。枚举默认从0开始,但是也可以人为指定,没有显式指定的枚举常量,取上一个值加1。
2).C语言中的枚举类型其本质就是一个整型,可以接受任何针对整型变量的赋值,不做与枚举值域有关的检查。
3).增加代码的可读性,避免重复定义常量。
39、空指针(void*)
1).代表任意类型的指针。
2).一切类型的指针都可以隐式转换为空指针。
3).对空指针不能解引用。
4).空指针支持计算,但是统一按单字节处理。
40、动态内存分配与释放
#include<stdlib.h>
void*malloc (
size_t size // 预分配字节数
);
成功返回所分配内存的起始地址,失败返回NULL。不初始化。
void*calloc (
size_t nmemb, // 元素数
size_t size // 元素字节数
);
成功返回所分配内存的起始地址,失败返回NULL。初始化为0。
所有通过动态内存分配所得到内存都要通过free()函数释放。
voidfree (
void* ptr // 内存地址
);
void*realloc (
void* ptr, // 原地址
size_t size // 新字节数
);
成功返回调整后的内存地址,失败返回NULL。新增部分不做初始化。
如果ptr为NULL,则与malloc()等效。
如果size为0,则与free()等效。
可能会分配新的内存空间,如果发生这种情况,原内存会自动释放,其内容会拷贝到新内存中,但是如果分配失败,原内存不会释放。
41、函数指针及其解引用
1).用指针存储代码区中的函数地址。所有的函数都有地址,而函数名实际上就是该函数的符号地址。
2).定义语法
函数:返回类型函数名 (形参表) {函数体;}
函数指针:
返回类型 (*函数指针名) (形参表);
42、解引用语法
函数指针名 (实参表);
pfoo(10, 3.14); // 调用bar
----------------
pfoo= &bar; (*pfoo) (10,3.14);
IO流的打开和关闭
FILE*fopen (
const char* filename, //文件路径
const char* mode // 打开模式
);
打开模式:
r:读
w:写
a:追加
r+:读写,文件必须存在,从头开始
w+:写读,文件不存在就创建,文件存在就清空
a+:读写,文件不存在就创建,文件存在就追加
b:二进制方式
UNIX/Linux:二进制方式作用不大
Windows:二进制方式,对读写内容不做任何转换。非二进制方式,写入\n,实际写入\r\n,文件中\r\n,读到的\n。
返回值是IO流结构体指针,用户调用其他IO流函数。
intfclose (
FILE* fp // fopen的返回值
);
成功返回0,否则返回EOF。
进程一启动,以下标准IO流是默认打开的:
stdout:标准输出
stdin:标准输入
stderr:标准出错
格式化IO
intfprintf (
FILE* stream, // IO流指针
const char* format,// 格式字符串
... // 输出数据项
);
printf(...)实际就是fprintf (stdout, ...)
格式字符串:
%[标志][宽度][.精度][h|l|ll]类型符
标志:
- 左对齐
+ 输出正负号
# 输出进制标识
0 用0填充
宽度.精度:123.34,宽度6,精度2,%6.2f
h short
l long/double
lllong long
类型符:
d 十进制整数
c 字符
s 空字符结尾的字符串
f 浮点数
x/X十六进制整数
o 八进制整数
p 地址(十六进制)
u 无符号十进制整数
g 浮点数(自动选择最短形式)
e 浮点数(科学计数法)
intfscanf (
FILE* stream, // IO流指针
const char* format, // 格式字符串
... // 地址表
);
1)以空白字符(空格、制表、换行)作为数据分隔符。
2)模式匹配:根据格式化字符串的模式进行匹配,从中提取数据。
3)禁止赋值:*
4)字符集表示:只读取某个特定字符集中的字符,只要遇到字符集以外的字符即刻返回。
非格式化IO
intfputc (
int c, // 字符
FILE* stream // IO流指针
);
成功返回c,失败返回EOF。
intfgetc (
FILE* stream // IO流指针
);
成功返回读到的字符,失败或者遇到文件尾返回EOF。
43、二进制IO
size_t fread (
void* buf, // 缓冲区指针
size_t size, // 期望读取的数据单元字节数
size_t count, // 期望读取的数据单元数
FILE* stream // IO流指针
);
成功返回实际读取的数组单元数。失败或遇到文件尾返回0。
size_tfwrite (
void* buf, // 缓冲区指针
size_t size, // 期望写入的数据单元字节数
size_t count, // 期望写入的数据单元数
FILE* stream // IO流指针
);
成功返回实际写入的数据单元数,失败返回0。
44、流位置与随机访问
intfseek (
FILE* stream, // IO流指针
long offset, // 偏移量
int origin // 偏移起点
);
成功返回0,失败返回-1。
origin:
SEEK_END- 从文件尾开始
SEEK_SET- 从文件头开始
SEEK_CUR- 从当前位置开始
fseek(fp, 10, SEEK_SET);
将文件流位置从文件头向文件尾方向移动10个字节。
fseek(fp, -10, SEEK_END);
将文件流位置从文件尾向文件头方向移动10个字节。
fseek(fp, 5, SEEK_CUR);
将文件流位置从当前位置向文件尾方向移动5个字节。
longftell (
FILE* stream // IO流指针
);
返回文件流的当前位置。
文件流位置从0开始。
45、定义名字空间
namespace名字空间名 {
名字空间成员;
}
访问名字空间
1)通过作用域限定运算符“::”
2)名字空间指令
usingnamespace 名字空间名;
可以省去名字空间前缀,但是注意不要因此引入歧义错误。
3)名字空间声明
using名字空间名::名字空间成员;
4)任何没有被置于某名字空间中的全局变量、函数、类型等均处于无名名字空间中。以如下方式访问之:
::标识符
46、C++的结构、联合和枚举
1).结构中可以定义函数,谓之成员函数,在成员函数中可以直接访问成员变量。
2).可以定义匿名联合变量。
3).枚举是一个独立的数据类型。
47、C++的布尔类型
bool的值域:true、false。
bool类型的变量可以接受任意类型的表达式,其值非零则为true,零则为false。
48、重载:在同一个作用域中,函数名相同,形参表不同的函数之间构成重载关系。
C++编译器通过对函数名按照形参表做编码替换区分不同的重载版本。
如果希望C程序调用C++模块中的函数,需要禁止C++对函数名进行替换:
extern"C" {
函数1 {...}
函数2 {...}
...
}
一个函数指针究竟指向哪个重载版本由函数指针的类型(包括形参表)决定,而不由调用实参表决定。
缺省参数:如果某个参数具有缺省值,那么其右边的所有参数必须都有缺省值。
注意不要因为使用缺省参数导致重载歧义。
49、哑元:只有类型而没有变量名的形参。
50、内联:用函数的二进制代码模块替换函数调用语句,进而节省函数调用的时间开销,其代价是增加目标模块的体积。
建议内联关键字:inline,编译器会根据实际情况决定是否真的做内联。复杂函数、递归函数不内联。
51、C++的动态内存分配:new运算符用于动态内存分配,delete运算符用于动态内存释放。
52、C++的引用
1)引用就是别名。
inta = 10;
int&b = a; // b就是a的别名
b++;
cout<< a << endl; // 11
2).将引用用于函数的参数,可以修改实参变量的值,可以减小函数调用的开销,避免虚实结合过程中对实参值的复制。
3).将函数的形参定义为常引用,可以在提高传参效率的同时,防止对实参的意外修改,而且还可以接受常量型的实参对象。
4).不要从函数中返回对局部变量的引用,因为该引用所引用的内存会在函数返回以后随着函数栈一起被释放。
5).引用与指针
a)引用的本质就是指针。
b)指针可以不做初始化,其目标可以在初始化以后随意改变(除非是指针常量),而引用必须做初始化,且一旦初始化其所引用的目标不能再改变。
c)可以定义指针的指针(二级指针),但是不能定义引用的指针。
d)可以定义指针的引用,但是不能定义引用的引用。
e)可以定义指针数组,但是不能定义引用数组,可以定义数组引用。
f)和函数指针一样,也可以定义函数引用,其语法特征与指针完全相同。
53、类型转换运算符
1).静态类型转换:
目标类型标识符 = static_cast<目标类型> (源类型标识符);
主要用于将void*转换为其它类型的指针。
动态类型转换:
目标类型标识符 = dynamic_cast<目标类型> (源类型标识符);
主要用于就有多态特性的父子类对象指针或引用之间的转换。
2).重解释类型转换:
目标类型标识符 = reinterpret_cast<目标类型> (源类型标识符);
inta;
double*p = reinterpret_cast<double*> (&a);
在不相关的指针或引用类型之间相互转换。
3).常类型转换:
目标类型标识符 = const_cast<目标类型> (源类型标识符);
去除一个指针或引用的常属性。
54、类和对象
类:对拥有相同属性和行为的对象进行归纳和抽象。万物皆对象。
1)类的定义
class类名 {
};
2)成员变量:描述属性
class类名 {
类型成员变量名;
};
3)成员函数:描述行为
class类名 {
返回类型成员函数名 (形参表) {
函数体;
}
};
4)访问控制:
public:公有成员,谁都能访问。
private:私有成员,只有自己能访问。
protected:保护成员,只有自己和自己的子类能访问。类的缺省访问控制为私有,而结构的缺省访问控制为公有。
55、构造函数
class类名 {
类名 (形参表) {
构造函数体;
}
};
构造函数没有返回类型,函数名与类名相同。构造函数无需亦无法直接调用,在创建对象的过程中,构造函数自动被执行,完成对象的初始化。
1)构造函数的参数来自创建对象时所提供的构造实参表。
2)构造函数可以通过形参表的不同实现重载。在创建对象的过程中,根据所提供构造实参表的不同,匹配到相适应的构造函数版本。
3)构造函数可以不带任何参数,这样的构造函数通常被称为无参构造函数或缺省构造函数。请注意,无参构造函数或缺省构造函数未必一定无参。
4)如果一个类没有定义任何构造函数,系统就会自动提供一个无参构造函数,在这个构造函数中完成基本的初始化工作。如果定义了构造函数,则系统不再提供构造函数。
5)构造函数的工作过程:
S1:创建并初始化基类部分;
S2:创建并初始化成员变量;
S3:执行构造函数中的代码。
6)初始化表
A.可以在定义构造函数的时候通过初始化表指示编译器对各个成员变量的初始化方式。
B.必须使用初始化表的场合:
a)类中含有缺少无参构造函数的类类型成员或者基类。
b)类中含有引用或者常量型的成员变量。
C.成员变量的初始化顺序依据其被声明的顺序,而非在初始化表中出现的顺序。
56、声明与实现分离的类定义
1).将父类的声明放在.h文件中,而将类的实现放在.cpp文件中。
2).如果一个类的成员函数在类的声明部分进行定义,则缺省为内联函数,如果成员函数在类声明的外部定义,则缺省为非内联。对于后者如果在其声明部分使用inline关键字,则表明建议编译器对该函数做内联处理。
57、this指针
1).对于一个对象,在其所属类型的成员函数中,都有一个隐含的this指针,该指针指向调用该函数的对象——调用对象。对于构造函数而言,其this指针就指向这个正在被构造的对象。
2).在构造函数中可以通过this指针区分同名的构造形参和成员变量。
3).通过返回*this,实现返回对调用对象自身的引用,满足级联式函数调用的需要。
4).通过将this指针做为函数的参数,实现不同对象间的交互。
58、常量型成员函数与常量型对象
1).常量型成员函数中的this指针是一个常量指针,以此防止对调用对象的意外修改。
2).被声明为mutable的成员变量可以在常量型成员函数中被修改。
3).常量型成员函数与非常量型成员函数可以构成重载关系,通过常量型对象(或指针、引用)调用常量型成员函数,通过非常量型对象(或指针、引用)调用非常量型成员函数。
4).通过常量型对象(或指针、引用)只能调用常量型成员函数。通过非常量型对象(或指针、引用)优先选择非常量型成员函数,如果没有非常量型成员函数,也可以调用常量型成员函数。
59、析构函数
1)当一个类类型对象被销毁的时候(局部变量离开作用域、动态创建堆对象被delete),析构函数被自动执行。
2)语法形式
class类名 {
~类名 (void) {
析构函数体;
}
};
没有参数,不能重载。
3)如果在构造函数动态分配了资源,那么就需要自己定义析构函数,并在其中释放上述资源。如果没有定义析构函数,那么系统就会提供一个缺省析构函数,但是缺省析构函数只负责释放成员变量,而不释放动态分配的资源。
4)析构函数的执行过程
S1:执行析构函数中的代码
S2:释放成员变量
S3:释放基类部分
60、拷贝构造函数与拷贝赋值运算符
1)所谓拷贝构造就是以拷贝的方式进行构造。
Students1 (...);
Students2 (s1); // 拷贝构造
Students2 = s1; // 拷贝构造
构造s1的副本s2
2)缺省方式的拷贝构造对于基本类型的成员变量,按字节复制,对于类类型的成员变量,执行相应类型的拷贝构造。
3).对于含有手动分配资源的对象缺省拷贝构造函数往往只能实现浅拷贝,为了获得基于深拷贝的完整副本,常常需要自定义拷贝构造函数。
类名::类名 (const 类名& that) {...}
4)拷贝构造的时机
a)直接构造对象副本
Students2 (s1);
Students3 = s2;
b)以对象为参数调用函数
voidfoo (Student s) {
...
}
Students;
foo(s);
c)从函数中返回对象
Studentfoo (void) {
Student s (...);
...
return s;
}
d)以对象的方式捕获异常
try{
...
}
catch(MyException ex) {
...
}
现代C++编译器倾向于对拷贝构造过程进行优化,基本原则就是尽可能地不做拷贝构造。
61、拷贝赋值
Students1 ("张飞", 28);
Students2 ("赵云", 25);
s2= s1; // 拷贝赋值
s2.print(); // 张飞,28
1).缺省方式的拷贝赋值对于基本类型的成员变量,按字节复制,对于类类型的成员变量,执行相应类型的拷贝赋值。
2).对于含有手动分配资源的对象缺省拷贝赋值往往只能实现浅拷贝,为了获得基于深拷贝的完整副本,常常需要自定义拷贝赋值函数。
类名& 类名::operator= (const 类名& that) {...}
释放原内存
分配新内存
拷贝源内容
返回自引用
防止自赋值
62、类的静态成员
1.与属于对象的非静态成员不同,类的静态成员是属于类的,而不属于对象。
1)静态成员变量的定义和初始化,只能在类的外部进行,而不能在构造函数中进行。
2)静态成员变量被该类的多个对象实例所共享。
3)静态成员函数只能访问静态成员,而非静态成员函数既可以访问非静态成员,也可以访问静态成员。
4)访问静态成员,既可以通过类,也可以通过对象,但是最好用类。
5)静态成员和非静态成员一样也受类的作用域和访问控制的限制。
63、单例模式
1)通过构造函数私有化,禁止在类外部自由创建对象。
2)通过静态成员控制对象的创建。
3)通过引用计数控制对象的销毁。
64、类的成员指针
类的成员指针
Studentstudent (...);
string*pstr=&student.m_name;
pstr不是成员指针!
1)指向成员变量的指针
定义:成员变量的类型类名::*指针变量名;
stringStudent::*pstr;
赋值:指针变量名=&类名::成员变量名;
pstr= &Student::m_name;
解引用:对象.*指针变量名、对象指针->*指针变量名
cout<< student.*pstr;
cout<< ps->*pstr;
2)指向成员函数的指针
定义:成员函数返回类型 (类名::*指针变量名) (形参表);
void(Student::*pfunc) (void);
赋值:指令变量名=&类名::成员函数名;
pfunc= &Student::who;
解引用:(对象.*指针变量名) (实参表)、(对象指针->*指针变量名) (实参表)
(s1.*pfunc)();
(ps->*pfunc)();
3)指向静态成员的指针与普通指针没有区别,不需要特殊的语法。
65、操作符与操作符函数
1)对于表达式L#R,如果L和R中至少有一个是类类型的对象,那么编译器就会将以上表达式处理为如下函数调用:
L.operator#(R); // 成员函数
operator#(L, R); // 全局函数
而该函数的返回值就是表达式的值。
2)输入输出操作符重载
Complexa (...);
cout<< a;
//cout.operator<< (a);
//operator<< (cout, a);
cin>> a;
3)单目操作符重载
#O==> O.operator# ()
==> operator# (O)
4)自增减操作符重载
前缀:Complex c (...);
++c==> c.operator++ ()
表达式的值是自增减以后的值。
可以连用:++++c;
后缀:Complex c (...);
c++==> c.operator++ (0)
表达式的值是自增减以前的值。
不可连用:c++++; 编译错误
5)不能重载的操作符
::- 作用域限定
. - 成员访问
.*- 成员指针解引用
?:- 三目运算符
sizeof()- 获取字节数
typeid()- 获取类型信息
不能通过操作符重载发明新的操作符
Complexoperator**(int x) {} // error !
操作符重载尽可能不要改变操作符的原始语义。
6)下标操作符重载
通常会分别针对下标运算的值作为左值和右值两种情况提供相对应的重载函数。形如:
元素类型&operator[] (size_t i) {...}
const元素类型& operator[](size_t i) const {...}
下标操作符多数情况下应用在具有连续存储特性的容器类型中。
7)、函数操作符重载
返回类型operator() (形参表){...}
定义了函数操作符的类型所实例化的对象可以被当做函数使用,其参数表和返回值由上述operator()的参数表和返回值决定。
8)解引用和间接操作符重载
解引用:*
间接:->
智能指针
9)自定义类型转换和类型转换操作符重载
(a).在目标类型中提供一个以源类型为参数的构造函数。
例如:
Integer::Integer(int data) {...}
int-> Integer
通过引入explicit关键字可以强制该类型转换为显式。
(b).在源类型中提供一个以目标类型为函数名的类型转换运算符。
例如:
Integer::operatorint (void) {...}
Integer-> int
66、继承的基本概念和语法
继承语法:class 子类名 : 继承方式1 基类1, 继承方式2 基类2, ... {...};
classPerson {...};
classStudent : public Person {...};
classTeacher : public Person {...};
67、公有继承的基本特点
1).一个子类类型的对象在任何时候都可以作为一个基类类型的对象看待。从一个子类类型对象的指针或引用到其基类类型的指针或引用不需要显示类型转换。
2).基类类型的指针或引用不能通过隐式类型转换得到子类类型的指针或引用(编译错误),但是通过显式类型转换可以避免语法上的限制,由此带来的运行时风险要有程序设计者自己评估。
68、继承方式对访控属性的影响
三种继承方式:public、protected、private
当通过一个子类对象访问其从基类中继承的成员时,需要考虑继承方式对访控属性的影响。
69、子类的构造函数与析构函数:
(1)子类没有定义构造函数,系统为子类提供一个无参构造函数,该构造函数会调用基类的无参构造函数构造子类对象中的基类子对象。前提是,基类中要有无参构造函数。
(2)若子类的构造函数中没有显式指明其基类部分如果被构造,则系统会调用基类的无参构造函数构造子类对象中的基类子对象。
(3)若子类的构造函数在其初始化表中以如下方式:
基类类名 (构造实参表)
指明了其基类的构造方式,则系统会根据重载匹配原则,在基类中选择适当的构造函数构造子类对象中的基类子对象。
(4)子类对象的构造顺序:
基类子对象->成员变量->子类的构造代码
(5)任何时候子类的析构函数都会自动调用基类的析构函数析构子类对象中的基类子对象。但是基类的析构函数不会自动调用子类的析构函数。因此delete一个指向子类对象的基类指针,可能引发内存泄露!!!
(6)子类对象的析构顺序:
子类的析构代码->成员变量->基类子对象.
70、子类的拷贝构造函数与拷贝赋值运算符函数
(1)缺省方式拷贝构造和拷贝赋值会自动调用基类的拷贝构造和拷贝赋值,以保证子类对象副本中的基类子对象得到正确的复制。
(2)自定义的拷贝构造和拷贝赋值需要显式地指明基类部分的拷贝构造和赋值方式,否则子类对象副本中的基类部分或者以无参方式被构造,或者得不到赋值。
71、名字隐藏
子类中存在与基类中完全相同的标识符即构成隐藏。通过“基类名::”可以指明访问基类中被隐藏的标识符。
72、多重继承与钻石继承
(1)名字冲突问题
用过类名+作用域限定
(2)钻石继承问题
钻石继承:子类继承自多个基类,而这些基类又源自一个共同的基类。
公共基类子对象在每个中间子类中各有一个实例,因此通过不同继承路径访问公共基类子对象中的数据会有不一致的问题。
解决:通过虚继承,使公共基类子对象在最终子类对象中只有一0份实例,而所有中间子类对象共享该实例。
虚继承:virtual、最终子类的构造函数可能需要显式指明公共基类子对象的构造方式。
73、虚函数与多态的基本概念
(1)在基类中将一个成员函数声明为虚函数(在返回类型前面加上virtual关键字),其子类中的同原型成员函数就也成为虚函数,并且对基类中的版本形成覆盖。此时,通过一个指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际被调用的是子类中的覆盖版本。这种语法现象被称为多态。
虚函数——覆盖——多态。
(2)虚函数覆盖的限制
1)基类中的成员函数必须被声明为虚函数,子类中的成员函数与基类中虚函数的函数名、形参表、常属性完全相同。
2)如果基类中虚函数的返回类型是基本类型,那么子类中覆盖版本的返回类型必须与基类版本完全相同。
3)如果基类中虚函数的返回类型是类类型的指针或引用,那么子类中覆盖版本的返回类型也可以是基类版本返回类型的子类。
4)子类中的覆盖版本不能比基类中的虚函数抛出更多的异常。
5)子类中覆盖版本的访控属性与基类无关。
(3)注意严格区分重载、隐藏和覆盖。
classVisitor {
public:
virtual bool visit (int credit); // A
virtual bool visit (double cash); // B
};
classValidVisitor : public Visitor {
public:
bool visit (int credit); // C
};
classMyValidVisitor : public ValidVisitor {
public:
bool visit (double cash); // D
};
classDumy : public Visitor {
public:
void visit (int credit); // E
};
B和A:重载
C和A:覆盖
C和B:隐藏
D和C:隐藏
D和B:覆盖
E和A:错误的覆盖
(4)纯虚函数、抽象类与纯抽象类
纯虚函数:在基类中被定义为如下形式的成员函数:
virtual返回类型成员函数名 (形参表)=0;
被称为纯虚函数。
抽象类:至少包含一个纯虚函数的类。抽象类不能被实例化为对象。抽象类的子类如果没有对基类中所有的纯虚函数提供覆盖定义,那么该子类也是抽象类。
纯抽象类:完全由纯虚函数(除了构造和析构函数)组成的抽象类。
(5)虚函数表与动态绑定:
虚函数表是一个存放虚函数地址的函数指针数组。每个由包含虚函数的类所创建的对象中都有一个指向该虚函数表的指针。当通过指向子类对象的基类指针,或者引用子类对象的基类引用调用基类中的虚函数时,实际上是根据该指针或引用实际指向的对象中的虚函数表获取函数地址并调用的。
(6)多态的前提和限制
多态=虚函数+指针或引用
在基类的构造函数和析构函数中调用虚函数,没有多态性,实际调用的永远是基类版本。
74、类型转换:
(1)动态类型转换:dynamic_cast
在运行期对指针实际指向的类型(应具有多态性)做检查,以返回NULL表示转换失败。
(2)静态类型转换:static_cast
在转换源类型和目标类型之间只要有一个方向上可以做隐式转换,那么在另一个方向上就可以做静态转换。以编译错误的形式表示转换失败。
(3)重解释类型转换:reinterpret_cast
无论在编译期还是运行期均不做任何检查,使用时要十分慎重。
(4)获取类型信息
typeid ()运算符返回值是一个typeinfo类型(声明在typeinfo的头文件中)的对象。其中包括一个name的成员函数,通过该函数可以获得类型的字面名称。同时typeinfo对象还可以用于比较两个类型是否相同或不同。typeid可以获取指针所指向对象的实际类型,前提是该类型应具有多态性。
75、虚析构函数
(1)将基类的析构函数定义为虚函数。delete一个指向子类对象的基类指针将导致子类类型的析构函数被调用,该函数在释放完子类对象特有的资源之后,自动调用基类类型的析构函数,完整其基类部分的释放。
(2)如果一个类中存在虚函数,往往意味着该类可以多态方式被使用,因此该类的析构函数就应该被定义为虚函数。
76、异常
(1).错误处理与非本地控制转移
(2).异常处理语法
(3).异常处理流程
无异常:throw之后的语句执行,引发异常的语句之后的代码执行,catch块不执行。
有异常:throw之后的语句不执行,引发异常的语句之后的代码不执行,catch块执行。
(4).异常处理用法
1)抛出基本类型异常,根据异常的值区分不同的异常。
2)抛出类类型的异常,根据异常的类型区分不同的异常。
3)使用异常说明语句。在函数的参数表和左大括号之间写:
throw(异常类型1, 异常类型2, ...)
A.如果没有异常说明,表示该函数可以抛出任何异常。
B.如果异常说明为throw (),表示该函数不抛出任何异常。
C.如果函数抛出了异常说明以外的异常,该异常不可被调用函数的catch子句捕获。
4)继续抛出异常。
(5)构造函数中的异常:
1)构造函数可以抛出异常,而且某些时候还必须抛出异常以表达构造过程中发生的错误(因为构造函数没有返回值)。
2)如果一个对象在其构造过程中发生了异常,该对象即被不完整构造,这样的对象其析构函数是不会被执行的。
3)对于不完整对象中的普通成员变量,构造函数的回退机制可以保证其被正确地析构,但是动态分配(new/malloc)的指针或引用型对象,必须在抛出异常之前手动(delete/free)进行释放,除非使用了智能指针。
(6)析构函数中的异常
任何时候都不要在析构函数抛出异常。
析构函数中尽量捕获可能引发的异常。
77、I/O流
C++中的I/O流 应用程序 -用户开发
C++I/O流 -istream/ostream类
C语言I/O流-fopen/fread库函数
I/O子系统 -系统调用:open/read
驱动程序 -寄存器、中断控制
I/O硬件 -机电器件
78、基础数据结构
顺序表:数组,存储在连续的空间中,随机访问方便,空间要求高,插入删除效率低。
链表:数据存储在不连续的节点中,各节点彼此关联,空间利用率高,插入删除效率高,随机访问不方便。
79、堆栈
基本特征:后进先出。
基本操作:压入(push),弹出(pop)
实现要点:初始空间,栈顶指针,判空判满。
80、队列
基本特征:先进先出——FIFO
基本操作:压入(push),弹出(pop)
实现要点:初始化空间,前指针(front)用于弹出,后指针(rear)用于压入,循环使用,判空判满。
81、链表
基本特征:内存中不连续的节点序列,节点之间通过指针彼此关联,有一个节点出发可以获得与其相邻的节点。
基本操作:插入/追加、删除、遍历/伪随机访问。
实现要点:维护节点指针之间的正确指向。
分类:单向线性链表、单向循环链表、双向线性链表、双向循环链表、其它链表十字链表
82、、二叉树
基本特征:
1)表达树形结构的最简模型,每个节点最多有两个子节点——左子节点和右子节点。
2)单根。每个子节点有且仅有一个父节点,整棵树只有一个根节点。
3)具有递归的结构特征,用递归的方法处理往往可以简化算法。
基本操作:生成、遍历。
实现要点:
1)由父及子的生成顺序。
2)三种遍历序
前序遍历:D-L-R 中序遍历:L-D-R 后序遍历:L-R-D
有序二叉树(二叉搜索树):
对于树上的任意节点都满足左子树中的数据<节点数据<右子树中的数据。
对有序二叉树做中序遍历,将获得一组有序的数据序列。
在有序二叉树中进行查询,可以极大地提高效率。
83、冒泡排序:
(1)算法
1)比较相邻元素,如果第一个比第二个大,就交换它们的位置;
2)对每一对相邻的元素做同样的工作,从开始的第一对到结尾的最后一对。经过这一步,最后的元素将是最大值;
3)针对所有的元素重复以上步骤,除了最后一个;
4)持续每次对越来越少的元素重复以上步骤,直至没有元素需要交换为止。
(2)评价
平均时间复杂度:O(N^2)
对数据的有序性敏感。
84、插入排序
(1)算法
1)从第一个元素开始,该元素可以认为已经有序;
2)取出下一个元素,在已经排好序的元素序列中从后向前扫描;
3)若该元素大于新元素,则将该元素复制到其下一个位置;
4)若该元素小于新元素,则将新元素插到该元素之后;
5)重复步骤2),直到处理完所有的元素。
(2)评价
平均时间复杂度:O(N^2)
对数据的有序性敏感。冒泡排序是值的交换,而插入排序是值的复制,因此插入排序要优于冒泡排序。
85、选择排序
(1)算法
首先在未排序序列中找到最小元素,与该序列的第一个元素交换位置。然后再从剩余未排序系列中继续寻找最小元素,与该序列的第一个元素交换位置。重复以上过程,直到处理完所有元素为止。
(2)评价
平均时间复杂度:O(N^2)对数据的有序性不敏感。虽然比较的次数多,单交换的次数少,因此略优于冒泡排序。
86、快速排序
任意选一个元素作为基准值:50
分组:将比基准小的放到基准值左侧,将比基准值大的房贷基准值右侧。
(1)算法
1)从待排序序列中选出一个元素,作为基准;
2)重新排列序列中的元素,所有比基准值小的元素位于基准值左侧,所有比基准值大的元素位于基准值右侧,与基准值相等的元素可以位于任意一侧,这个过程叫做分组;
3)以递归的方式对小于基准值的分组和大于基准值的分组分别进行排序。
(2)评价
平均时间复杂度:O(NlogN)
如果每次分组都能做到均分,快速排序是最快的排序算法。
87、线性查找
(1)算法
从表头开始,依次将每一个元素的值与查找目标进行比较,最后,或者找到目标,或者没有找到。
(2)评价
平均时间复杂度:O(N)
对数据本身没有规则性要求。
88、折半查找
(1)算法
首先,假设表中元素按升序排列,将表中间位置的元素与查找目标做比较,如果二者相等,则查找成功,否则利用中间元素将表分为左右两部分。如果中间元素大于查找目标,则在左子表中继续查找,反之在右子表中继续查找。重复以上过程,直到找到满足条件的元素,或直到子表不存在为止,此时查找失败。
(2)评价
平均时间复杂度:O(logN)
对数据本身有规则性要求:必须要有序。
数据必须以顺序表的形式存储。
89、归并排序
平均时间复杂度:O(2NlogN)
需要额外的辅助空间。
90、类模板、函数模板与模板特化
1)模板的两步实例化:
类模板------>具体类------>对象
编译期 运行期
函数模板------>具体函数------>调用
编译期 运行期
2)模板特化就是针对某种特定的类型,是通用模板获得相应的特殊的定义。
(1)类模板
template<class类型形参1, class 类型形参2, ...> class 类模板名 {...};
如:template<class T1, class T2, classT3> class Dumy {
T1 foo (T2 t2) {
T3 t3;
...
}
};
在类声明外部定义成员函数:
template<classT1, class T2, class T3>
T1Dumy<T1, T2, T3>::foo (T2 t2) {
T3 t3;
...
}
(2)类模板特化
template<>class 类模板名<类型实参1, 类型实参2, ...> {...};
如:
template<>class Dumy<char,short,int> {
char foo (short t2) {
int t3;
...
}
};
在类声明外部定义成员函数:
charDumy<char,short,int>::foo(short t2) {
int t3;
...
}
(3)类成员函数特化
template<>
返回类型类模板名<类型实参1,类型实参2,...>::成员函数名 (形参表) {...}
如:
template<>
charDumy<char,short,int>::foo(short t2) {
int t3;
...
}
(4)函数模板
template<class类型形参1, class 类型形参2, ...>
返回类型函数模板名 (形参表) {...}
如:
template<classT1, class T2, class T3>
T1foo (T2 t2) {
T3 t3;
...
}
(5)函数模板特化
template<>
返回类型函数模板名<类型实参1, 类型实参2, ...> (形参表) {...}
如:template<> char foo<char,short,int>(short t2) {
int t3;
…
91、模板的局部特化与最优匹配
(1)编译器优先选择特化程度最高的版本。
(2)编译器优先选择针对指针的特化版本。
(3)编译器优先选择参数匹配程度最高的特化版本。
92、模板的非类型参数与缺省参数
(1)一个模板类或者模板函数除了可以接受类型参数以外,还可以接受非类型参数,但是对应的实参必须是常量表达式。
(2)模板类或者模板函数也可以带有缺省参数,但是如果一个模板参数有缺省值,该参数后边的所有参数必须都有缺省值。
93、数组与链表的优缺点
1)数组:随机访问高效,空间要求高,插入删除不便。
2)链表:空间要求低,插入删除方便,随机访问困难。
C++的STL提供了一套容器模板,使得对数据结构的底层操作对程序设计者透明化,同时结合了数组和链表各自优点,以尽可能简单的方式获得尽量高效的使用效果。
94、十大容器
1)向量(vector)
支持对数据元素的下标访问,在容器的尾部进行插入和删除效率很高。
2)列表(list)
高效地在任意位置做插入和删除,不支持对数据元素的下标访问。
3)双端队列(deque)
支持对数据元素的下标访问,在容器的头部和尾部进行插入和删除效率很高。
4)堆栈(stack)
只支持在容器的一端存储和提取数据元素。
5)队列(queue)
支持从后端压入从前端提取数据元素。
6)优先队列(priority_queue)
提取的永远是当前容器中优先级最高的数据元素。
7)映射(map)
以key的升序存储key-value对的序列。每个key只能出现一次。根据key提取value效率非常高。
8)集合(set)
映射的低级形式,每个数据元素只有key没有value。
9)多重映射(multimap)
key不唯一的映射。
10)多重集合(multiset)
key不唯一的集合。
95、容器的分类
1)线性容器:向量、列表、双端队列。
2)适配器容器:堆栈、队列、优先队列。
3)关联容器:映射、集合、多重映射、多重集合。
96、容器的共同特征
1)所有的容器类型支持拷贝构造和拷贝赋值,这样一个容器类型的对象可以作为一个整体被复制给另一个相同类型的容器对象,前提是容器中元素类型也要支持拷贝构造和拷贝赋值。
2)所有的容器类型支持“==”运算符。两个容器类型对象相等的条件:容器类型相同、容器中数据元素的类型相同,元素个数相同,对应元素之间满足“==”运算符的判断。
3)所有的容器类型支持“<”运算符。两个相同类型容器进行“<”比较,其中一个容器中的每一个数据元素都要“<”另一个容器中对应位置的元素。
4)容器所存放的对象都是对象的副本,因此可能存放在容器中的元素类型一般都要支持缺省构造、拷贝构造和拷贝赋值运算符。
97、向量(vector)基本特点
1)内存连续与下标访问。
2)动态内存管理。
3)通过内存预分配减小动态内存管理的额外开销。
4)支持插入和删除(虽然效率不高)。
98、定义变量
#include<vector>
vector<int>vn;
vector<string>vs;
vector<vector<int>> vvn;
随机访问
vn.push_back(10);
vn.push_back(20);
cout<< vn[0] << endl; // 10
cout<< vn[1] << endl; // 20
for(int i = 0; i < vn.size (); i++)
cout << vn[i] << endl;
迭代访问
vector<int>::iteratorit = vn.begin ();
cout<< *it << endl; // 10
it++;
cout<< *it << endl; // 20
for(vector<int>::iterator it = vn.begin (); it != vn.end (); it++)
cout << *it << endl;
预分配与初始化
vector<int>vn (200);
vector<int>vn (200, 2);
classStudent {...};
vector<Student>vs (200);
vector<Student>vs (200,
Student("张飞", 28));
front()/back()成员函数分别返回向量容器中第一个和最后一个数据元素。
size()/resize()/clear()/capacity()/reserve()
size()- 返回向量的大小,即数据元素的个数。
resize()- 改变向量的大小,可能引发类类型数据元素的析构或构造。
clear()- resize(0),清空向量中所有的数据元素。
capacity()- 返回向量的容量,即最多可容纳数据元素的个数。
reserve()- 人为地多预留一些内存空间。只能增长不能减少内存。多分配的内存不做初始化,不会调用类类型数据元素的构造函数。
1)向量的大小可以增加也可以减少,引起向量大小变化的成员函数除了resize()以外还有push_back()/pop_back(),以及insert()/erase()。
2)向量的容量只能增加不能减少,只能通过reserve()成员函数人为地改变向量的容量。
3)向量的大小的增加可能会导致容量的增加,但是容量的变化并不会引起大小的变化。
4)通过resize()增加向量的大小,新增部分会被初始化,但是通过reserve()增加向量的容量,新增内存并不做初始化。
5)位于向量容量范围内但是却不在其大小范围内元素,可以通过下标或者迭代器访问,但是其值往往是不确定的。
6)无论是resize()还是reserve(),他们所引起的向量的大小或容量的改变,都发生在容器的尾部。
insert()/erase():这两个函数都是以迭代器作为参数,表示插入和删除的位置。
find()/sort():全局函数
通过迭代器构造向量容器
更多推荐
c语言的总结归纳
发布评论