第1章 预备知识
- C++融合了3种不同的编程方式:C语言代表的过程性语言、C++在C语言基础上添加的类代表的面向对象语言、C++模板支持的泛型编程。
- 面向过程强调算法,面向对象强调数据。类是一种规范,它描述了这种新型数据格式,对象是根据这种规范构造的特定数据结构。即类是抽象的,对象是具体的。类是模板,对象是实例。
第2章 开始学习C++
- C++能够使用printf()、scanf()和其他所有标准C输入和输出函数,只需要包含常规C语言的stdio.h文件。
- namespace的使用方法:
using namespace std; 所有的名称都可以使用
using std::cout; 只使用std里面的cout。 - 类是用户定义的一种数据类型。
- C++程序应当为程序中使用的每个函数提供原型。C++不允许将函数定义嵌套在另一个函数定义中。
- main()函数的返回值返回给操作系统。即返回1则程序正确执行。
第3章 处理数据
- 面向对象编程(OOP)的本质是设计并扩展自己的数据类型。
- 基本类型:整数和浮点数; 复合类型:数组、字符串、指针和结构。
- 最小长度:short:16位;int:至少与short一样长;long:至少32位,且至少与int一样长;long long:至少64位,且至少与long一样长。(1个字节8位)
- 整数相加减时,如果超越了限制,其值将为范围另一端的取值。例:unsigned short类型的0-1将得到65535。
- cin只接受输入流中的第一个字符,其余字符会继续存放在流中,后续可以使用。
- 浮点数在计算机中分成两部分存储,一部分表示值,另一部分用于对值进行放大和缩小。
- 浮点数的缺点: 浮点数运算的速度比整数的慢,并且精度会降低。
例:
1 | float a = 2.34E+22f; |
输出的结果应为1,但实际的输出值为0。因为a是一个小数点左边有23位的数字,加上1,即在第23位加1,而float类型只能表示数字中的前6位或前7位,即有效位为6或7,因此修改第23位对这个值并没有任何影响。
对于float,C++只能保证6位有效位。
- C++会自动执行很多类型转换,但当较大整型转换为较小的整型时,原来的值 可能超出目标类型的取值范围,通常只复制右边的字节,即高位将被截掉。
例:
1 | int a = 7.2E12; |
由于a的值超过了int的最大取值范围,因此在某些操作系统上,得到的值为2147483647(int类型的最大值)。
- C++类型转换:typeName(value)
第4章 复合类型
- 只有在定义数组时才能使用初始化,初始化时可以省略等号,不能将一个数组赋给另一个数组。如果只对数组的一部分初始化,则其他位置为0,如果初始化数组时,方括号内为空,则编译器会计算元素个数。
例:
1 | short a[] = {1, 2, 3, 4}; |
编译器会计算short型数组包含4个元素。
short指定了类型,a表示变量名,[]表明是一个数组。
- 字符串与字符数组
1 | char NJUT[4] = {'n', 'j', 'u', 't'}; |
这2个数组都是char数组,但只有第2个是字符串。即字符串必须是以’\0’结尾的字符数组。对于第2个字符数组,cout会打印’njut’,即遇到’\0’则停止,但对于第1个,会一直打印,直到遇到内存中有’\0’为止。
还有1种更简单的方法是用一个引号将这些字符括起来,即1
char NJUT[] = "njut";
这种称为字符串常量,编译器会自动将’\0’加上。”njut”指的是字符串所在的内存地址。
1 | char name[10] = "cxx"; |
strlen()返回的是存储在数组中的字符串的个数,即可见的字符个数,因此该返回值为3,而sizeof()计算的是整个数组的长度。如果要存储一个字符串,则数组的长度至少是strlen(字符串)+1。
- cin、cin.getline()和cin.get()
cin:使用空白(空格,制表符和换行符)来确定字符串的结束位置。所以cin只能1次读取1个单词(遇到空格会结束),剩下的部分会继续存放在cin流中,直到下次的读取。
cin.getline(name,size):函数读取整行,通过换行符确定字符串的结束位置,第1个参数为用来存储输入的数组名称,第2个为要读取的字符串的字符数,其值为size-1,即最后1位用于字符串结尾的空字符。
cin.get():其中一种形式get(name,size)和getline(name,size)类似,都读到行尾,但get()不会读取并丢弃换行符,而是会留在输入流中,这样就会存在一个问题,例:1
2cin.get(name1, 10);
cin.get(name2, 10);
当读完第1个名字后,由于get()并不会丢弃换行符,所以第2次读取的时候会直接读到换行符,从而会认为已经到达行尾,不会读取任何字符。为了避免这个问题,可以采用无参数的get()函数,即cin.get(),该函数读取下一个字符(换行符也可以),即采用下面的方法:1
2
3cin.get(name1, 10);
cin.get()
cin.get(name2, 10);
也可以将这2个函数拼在一起,即cin.get(name1, 10).get()。
- string对象和字符数组之间的主要区别是:可以将string对象声明为简单变量,而不是数组。
1
2string name;
string name = "cxx";
string相当于一个类,字符串相当于类的实例化,不需要在string后面加上长度。
- 创建结构体时首先要创建一个模板。例
1 | struct inflatable |
前一个为结构体的声明,后一个为结构体的赋值。结构体相当于用户自定义的一种结构类型。struct指定了类型为结构体,inflatable为这种新类型的名称。大括号的内容为结构体的成员。整体相当于一个对象的模板,后面的则是对象的实例化,guest为结构体的名称,其具体内容为大括号里面的内容。
注意,不管是声明还是赋值,都是C++的语句,需要加分号结束。
- 可以创建多个值相同的枚举量
1 | enum number {first, second, third = 1. forth = 100, fifth}; |
first在默认情况下为0,second和third都为1,fifth为101。枚举也相当于一个数据类型,初始化相当于模板,使用时需要先实例化,在调用里面的枚举值,即:1
2number mynumber;
mynumber = first;
类似于const限定符。
- 只有初始化指针时,才会出现*=&,其他情况,*指的是解除引用,即取出地址存放的值,&指的是地址运算符,即获得变量存在的地址。
1
2int a = 5;
int * pt = &a;
相当于1
2int * pt;
pt = &a;
int指定了类型为整型,pt是变量名,*表明是指针,和数组,结构体类似,指针也是个复合类型,需要同基本类型同时使用,即pt是一个整型的指针类型。
指针指的是地址,只有在初始化时才可以将*和&同时使用,否则指针变量后面的赋值一定是某个地址。
在对指针变量使用解除引用运算符(*)之前,一定要将指针初始化为一个确定的,适当的地址。否则指针将找不到该地址,将错误的地址,甚至是正在运行的程序地址返回并执行操作。
一定要配对的使用new和delete,否则会发生内存泄漏。delete只能删除new创建的指针。
指针和字符串
1
2char flower[10] = "rose";
cout << flower << "s are red.\n;
如果给cout提供一个字符的地址,则将从这个字符开始打印,直到遇到空字符为止。
- 指针与数组、字符串、结构体
1
2
3
4
5
6
7
8
9
10
11
12double wages[3] = {100.0, 200.0, 300.0};
double * p1 = wages;
char animal[10] = "bear";
char * p2 = animal;
struct info
{
int year;
};
info s01;
info * p3 = &s01;
指针在初始化时,后面需要赋值一个地址,数组名和字符串常量都是地址,所以不需要再加上取地址符。
- vector和array
1
2
3
4
5
6vector<typename> vt(n_elem);
array<typename, n_elem> arr;
double a1[4] = {1.2, 2.3, 3.4, 4.5};
vector<double> a2(4);
array<double, 4> a3 = {1.2, 2.3, 3.4, 4.5};
使用vector和array,需要添加头文件#include
- new创建指针
1
typeName * pointer_name = new typeName;
第5章 循环和关系表达式
- for循环:
1
2
3
4for (初始化;测试语句;更新语句)
{
主体;
}
测试语句可以是任意表达式,C++会将结果强制转换为bool类型。
C++中,在for和括号之间加上一个空格,以区别函数名和括号。
1
cout.setf(ios_base::boolalpha);
设置显示为布尔值。
第6章 分支语句和逻辑运算符
c++有if - else if - else 结构
isalpha():测试字符是否为字母字符,isdigits():测试字符是否为数字字符,isspace():测试字符是否为空白(换行符,空格和制表符),ispunct():测试字符是否为标点符号。
第7章 函数
如果声明的返回值类型为double,而函数返回一个int表达式,则该int值会将强制转换为double类型。
C++的返回值不可以是数组,但可以将数组作为结构或对象组成部分来返回。
在函数中使用指针来处理数组
1
2int sum_arr(int * arr, int n); //函数原型
int sum = sum_arr(cookies, 10); //函数调用
这里的cookies为数组名,表示数组中第一个元素的地址,而函数原型中,* arr表示的也是地址,当然也可以用arr[]替换,因为在c++中,当且仅当用于函数头或函数原型中,* arr 和 arr[]的含义是相同的。
在使用数组名作为参数时,并没有将数组的全部内容传递给函数,而是将数组的地址,包含的元素类型以及数目传递给函数,这样会大大减少内存空间。
- 指针常量和常量指针
1
2const int * pt = &age; //常量指针
int * const finger = &sloth; //指针常量
变量名左边是变量的类型,当有多个类型修饰时,看最近的。
变量pt最左边是*,代表的是指针,再往左是int,表明基本类型为整型,最左边是const,表明其值是一个常量。所以pt首先是一个指针,其存放的内容是变量age的地址,地址指向的是一个整型数据,且该数据是个常量,即const int 是共同修饰*pt的。所以变量pt指向的值是不可以改变的,即地址不可变。
而变量finger最左边是const,代表的是常量,再往左是 *,表明是一个指针,最左边是int,表明基本类型为整型。所以finger首先是一个常量,只是该常量指向的是一个int型的指针,所以其指向的变量不可以更改。1
2
3
4
5
6
7
8
9
10int gorp = 16;
int chips = 12;
const int * p_snack = &gorp;
*p_snack = 20; //禁止修改
p_snack = &chips; //可以修改
int * const p_snack = &gorp;
*p_snack = 20; //可以修改
p_snack = &chips; //禁止修改
第一个创建的是常量指针,所以指针指向的地址不可以修改,该例中,p_snack指针指向的是gorp的地址,而该地址存放的变量值是16,所以*p_snack的值只能是16,不能是其他值。但是指针指向的变量是可以更改的,即原来是指向gorp变量地址的,现在可以改为指向chips变量地址,但其值仍为16。虽然不能直接修改,但可以通过改变chips的值修改,比如将chips的值改为20,那么现在该常量指针的值就是20了。
第二个创建的是指针常量,所以指针指向的变量是不可以修改的。也就是变量p_snack是一个常量,其值是不可以修改的,只不过这个变量是指针,而指针的值是地址,所以p_snack只能指向chips变量,其值等于chips的地址,但可以修改该地址对应的值,即本来存放chips变量的地址,其值是12,可以修改为20。
二维数组与指针
1
arr[a][b] = *(*(arr +a) + b)
函数与字符串数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using namespace std;
void show_char(const char * a[]);
void show_string(string a[]);
const char* Seasons_char[4] = {"Spring", "Summer", "Autumn", "winter"};
string Seasons_string[4] = {"Spring", "Summer", "Autumn", "winter"};
void show_char(const char * a[])
{
for (int i = 0; i < 4; i++)
cout << a[i] << endl;
}
void show_string(string a[])
{
for (int i = 0; i < 4; i++)
cout << a[i] << endl;
}
int main(int argc, char const *argv[])
{
show_char(Seasons_char);
show_string(Seasons_string);
return 0;
}
创建字符串数组的两种形式。第一种形式时,必须要加const。相当于二维数组char Seasons_char[][4]。
- 函数与结构体/结构体指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using namespace std;
struct box
{
char maker[40];
float height;
float width;
float length;
float volume;
};
void show(box x)
{
cout << "maker: " << x.maker << endl;
cout << "height: " << x.height << endl;
cout << "width: " << x.width << endl;
cout << "length: " << x.length << endl;
cout << "volume: " << x.volume << endl;
}
void show_pointer(box * x)
{
x->volume = x->height * x->length * x->width;
cout << "maker: " << x->maker << endl;
cout << "height: " << x->height << endl;
cout << "width: " << x->width << endl;
cout << "length: " << x->length << endl;
cout << "volume: " << x->volume << endl;
}
void show(box x);
void show_pointer(box * x);
int main(int argc, char const *argv[])
{
box box1 = {"cxx", 1, 1, 1.2};
show(box1);
show_pointer(&box1);
return 0;
}
在C++中,结构体struct和整型int类似,也是一种数据类型,只不过是由用户自己定义的,所以可以将其看作是int型。
上面代码创建了2个函数,一个是按值传递结构体,一个是按地址传递结构体。在使用结构体时要先定义一个结构体模板。
按值传递的结构体中,函数头是void show(box x);表明其数据类型是box,而box就是定义好的结构体类型,相当于int x。在按值传递时,通过用点运算符访问成员变量,调用函数时,传递的参数为结构体的变量名,而按地址传递时,函数头是void show_pointer(box * x);表明其数据类型也是box,但是传递的是一个指针,也就是结构体变量的地址,在按地址传递时,需要用->运算符访问成员变量,调用函数时,传递的参数应该是结构体变量的地址,即在变量名前面加上取地址符。
函数调用时,传递的参数要么是变量名,要么是变量的地址,不需要加修饰符。
与普通的变量一样,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。函数名即函数的地址,后面的括号表示调用,不需要加取地址符。这样就可以将函数名作为一个参数传递给另一个函数。
1
2
3double pam(int); //函数声明
double (*pf)(int); //函数指针
double * pf(int); //指针函数
声明一个函数pam,有1个参数为int,返回值为double。用一个指针(*pf)来替代函数名就可以声明一个函数指针,也就是必须要指针函数的参数类型和返回值类型。此时pam和*pf一样,都是函数名,而pf就是函数指针,即函数的地址。
指针必须用括号括起来,否则pf会和后面的括号先结合,然后和*结合,变成指针函数,相当于创建一个pf(int)的函数,其返回值为double类型的指针。