C++学习笔记

C++讲义

第二章 变量和基本类型

wchar_t

在字符字面前加L就能够得到wchar_t类型的宽字符字面值

1
L'a'

const

const 限定符把一个对象转化为一个常量

1
const int bufSize = 512;

任何修改bufSize的尝试都会导致编译错误i,定义时必须初始化。

const默认为文件的局部变量

1
2
3
4
5
6
//file_1.cpp
int counter;

//file_2.cpp
extern int counter;
++counter;

除特别声明,在全局作用域声明的const变量是定义该对象文件的局部变量,不能被别的文件访问。

1
2
3
4
5
6
//file_1.cpp
extern const int bufSize = fcn();

//file_2.cpp
extern const int bufSize; //在这里bufSize的声明同样是extern; 这样extern 标志着bufSize是一个声明,所以没有初始化式
a = bufSize; //使用文件1中的bufSize

引用

引用就是对象的另一个名字,在实际程序中主要用作函数的形式参数。

引用是一种复合类型,在变量名前“&”来定义,不能定义引用类型的引用,但是能定义任何其他类型的引用

1
2
3
4
int ival = 1024;
int &refVal = ival; //√
int &refVa2; //× 必须初始化
int &refVa3 = 10; //× 初始化必须是对象

const 引用

const引用是指向const对象的引用:

1
2
3
const int ival = 1024;
const int &refVal = ival;//√
int &ref2 = ival;//× 会导致能够对ival进行修改

可以读取但是不可以修改refVal,因此对refVal的赋值都是不合法的。

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
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
int main()
{
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl; return 0; } ``` > 52

const引用可以初始化为不同类型的的对象或者初始化为右值

```c
int i = 1024;
const int &r = 1024;//对比前面
const int &r2 = r + i;//因为只读
</code></pre>

<h2>第三章 标准库类型</h2>

<h3>标准string类型</h3>

string类型支持长度可变的字符串

string s1(n,'c')//表示将s1初始化为字符'c'的n个副本

<blockquote>
由于历史原因以及为了与C语言兼容,字符串字面值与标准库string类型不是同一类型。
</blockquote>

<pre><code class="c">int main()
{
string line;
while(getline(cin,line))//一个输入流一个string对象
cout << line << endl; return 0; //由于getline 函数返回时丢弃换行符,换行符将不会存储在string对象中 } ``` ### string对象的操作 - s.empty() //如果是空串返回true - s.size() //串字符的个数 不要把size的返回值赋给一个int变量(unsigned) ### string对象中字符的处理 ``#include<cctype>`` - isalnum(c) //c是字母或数字 返回true - isalpha(c) //c是字母 - isgraph(c) //c不是空格,但可打印 - tolower(c)//如果c是大写则返回其小写字母,否则返回c - toupper(c) ### 标准vector类型 > P78

vector是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。

一个容器中所有对象必须是同一种类型的

- `vector<int> ivec;` 和其他变量定义一样,定义vector对象要指定类型和一个变量列表(int 类型对象vector 变量名为ivec)
- `vector<T> v(n,i)` v包含n个值为i的元素
- `vector<int> fvec(10)`

### vector 对象的操作

- v.empty()
- v.size()
- v.push_back(t)

大家认为vector的下标操作可以添加元素,以下是错误

```c
vector<int> ivec;
for(vector<int>::size_type ix=0;ix != 10;++ix)
ivec[ix] = ix;

但是ivec是vector对象,而且下标只能用于获取已存在的元素

正确写法:

1
2
for(vector<int>::size_type ix=0;ix!=10;++ix)
ivec.push_back(ix);

const char*char const*效果一样,都是不允许修改指针指向的地址空间的值,即把值作为常量,而char * const则是不允许修改指针自身,不能再指向其他地方,把指针自己当作常量使用。需要注意的是,使用char * const定一个常量指针的时候一定记得赋初始值,否则再其他地方就没法赋值了。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
using std::cout;
using std::endl;

int main()
{
const char *st = "The expense of spirit\n";
int len = 0;
while (*st) { ++len; ++st; }
cout << len << ": " << st << endl;
return 0;
}
</code></pre>

<pre><code class="c">#include <iostream>
using std::cout;
using std::endl;

const char *st = "The expense of spirit\n";

int main()
{
int len = 0;
const char *p = st;
while (*p) { ++len; ++p; }
cout << len << ": " << st << endl;
return 0;
}
</code></pre>

<pre><code class="c">#include <string>
using std::string;
#include <iostream>
using std::cout; using std::endl;

int main()
{
string substr = "Hello";
string phrase = "Hello World";
string slang = "Hiya";

if (substr < phrase) cout << "substr is smaller" << endl; if (slang > substr) cout << "slang is greater" << endl; if (slang > phrase) cout << "slang is greater" << endl;

return 0;
}
</code></pre>

<pre><code class="c">#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;

int main()
{
// empty vector
vector<int> ivec;
int val;
// give each element a new value
for (vector<int>::size_type ix = 0;
ix != 10; ++ix)
ivec.push_back(ix);

cout << "ivec.size: " << ivec.size() << endl; // prints 10

// reset the elements in the vector to zero
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
ivec[ix] = 0;

// is there anything to print?
if (ivec.empty() == false) {
cout << "vector contents: " << endl;
// print each element separated by a newline
for (vector<int>::size_type ix = 0;
ix != ivec.size(); ++ix)
cout << ivec[ix] << endl;
}
return 0;
}
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
#include <vector>
#include <string>
#include <iostream>

using std::vector;
using std::string;
using std::cin;
using std::cout;
using std::endl;

int main()
{
vector<int> ivec(10);

// reset all the elements in ivec to 0
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
ivec[ix] = 0;

// print what we've got so far: should print 10 0's
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
cout << ivec[ix] << " ";
cout << endl;

// equivalent loop using iterators to reset all the elements in ivec to 0
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
*iter = 0; // set element to which iter refers to 0

// print using iterators: should print 10 0's
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
cout << *iter << " "; // print the element to which iter refers
cout << endl;

vector<int>::iterator iter = ivec.begin();
while (iter != ivec.end()) {
*iter = 0;
++iter;
}
return 0;
}

迭代器

P84

除了使用下标来访问vector对象的元素外,标准库还提供了另外一种访问元素的方法。迭代器是一种检查容器内元素并遍历元素的数据类型。

  1. 容器的iterator类型
    vector::iterator iter;//定义了一个名为iter的变量,它的数据类型是由vector定义的iterator类型。

  2. begin和end操作
    end操作返回的迭代器并不指向vector中的任何实际元素,哨兵的作用。

  3. 应用程序示例

1
2
for (vector<int>::iterator iter = ivec.begin();iter !=ivec.end(); ++iter)
*iter = 0;
  1. const_iterator
    该类型只能用于读取容器内的元素,但不能修改他的值。

与const iteator的区别:
使用const_iterator类型时,得到一个迭代器,自身值是可以改变的,但不能用来改变其所指向的元素的值(可以自增但不能赋值)

const 的迭代器必须初始化迭代器,初始化后不能改变。

1
2
3
4
vector<int> nums(10);
const vector <int>::iterator cit = nums.begin();
*cit =1;
++cit;//×

const迭代器几乎没什么用,一旦初始化后只能用它来改写其指向的元素但不能指向别的元素。

bitset

P88

  • bitset<32> bitvec 初始化32位,每位都是0
  • bitset<n> b(u) b是unsigned long类型的一个副本
  • bitset<n> b(s)
  • bitset b(s,pos,n) b是s中从位置pos开始的n个位副本
  • string strval("1100")
  • bitset <32> bitset4(strval) 高阶置0,反向转化的
  • b.any()
  • b.none()
  • b.count()
  • b.size()
  • b[pos]
  • b.set() b.set(pos)
  • b.reset()
  • b.flip()
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <cstddef>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
using std::size_t;

#include <bitset>
using std::bitset;
int main()
{
bitset<32> bitvec; // 32 bits, all zero
bool is_set = bitvec.any(); // false, all bits are zero
bool is_not_set = bitvec.none(); // true, all bits are zero

cout << "bitvec: " << bitvec << endl;

size_t sz = bitvec.size(); // returns 32

size_t bits_set = bitvec.count(); // returns number of bits that are on

// assign 1 to even numbered bits
for (int index = 0; index != 32; index += 2)
bitvec[index] = 1;

// equivalent loop using set operation
for (int index = 0; index != 32; index += 2)
bitvec.set(index);

unsigned i;

cout << "bitvec: positions turned on:\n\t";
for (int index = 0; index != 32; ++index)
if (bitvec[index])
cout << index << " ";
cout << endl;

// equivalent; turn off first bit
bitvec.reset(0);
bitvec[0] = 0;

bitvec.reset(); // set all the bits to 0.
bitvec.set(); // set all the bits to 1

bitvec.flip(0); // reverses value of first bit
bitvec[0].flip(); // also reverses the first bit
bitvec.flip(); // reverses value of all bits

// leaves bitvec unchanged; yields a copy of bitvec with all the bits reversed
bitvec = ~bitvec;

// bitvec1 is smaller than the initializer
bitset<16> bitvec1(0xffff); // bits 0 ... 15 are set to 1

// bitvec2 same size as initializer
bitset<32> bitvec2(0xffff); // bits 0 ... 15 are set to 1; 16 ... 31 are 0

// on a 32-bit machine, bits 0 to 31 initialized from 0xffff
bitset<128> bitvec3(0xffff); // bits 32 through 127 initialized to zero
cout << "bitvec1: " << bitvec1 << endl;
cout << "bitvec2: " << bitvec2 << endl;
cout << "bitvec2[0] " << bitvec2[0] << endl;
cout << "bitvec2[31] " << bitvec2[31] << endl;
cout << "bitvec3: " << bitvec3 << endl;

string strval("1100");
bitset<32> bitvec4(strval);

cout << "strval: " << strval << endl;
cout << "bitvec4: " << bitvec4 << endl;

{
string str("1111111000000011001101");
bitset<32> bitvec5(str, 5, 4); // 4 bits starting at str[5], 1100
bitset<32> bitvec6(str, str.size() - 4); // use last 4 characters

cout << "str: " << str << endl;
cout << "bitvec5: " << bitvec5 << endl;

cout << "str: " << str << endl;
cout << "bitvec6: " << bitvec6 << endl;
}

{
unsigned long ulong = bitvec3.to_ulong();
cout << "ulong = " << ulong << endl;
}

bitset<32> bitvec7 = bitvec2 & bitvec4;

cout << "bitvec7: " << bitvec7 << endl;

bitset<32> bitvec8 = bitvec2 | bitvec4;

cout << "bitvec8: " << bitvec8 << endl;

cout << "bitvec2: " << bitvec2 << endl;
return 0;
}

第四章 数组和指针

指针的定义和初始化

string* 错误的理解成一种数据类型

string* ps1,ps2 实际上是ps1是指针,ps2是一个普通的string对象

未初始化的指针是无效的,直到给该指针赋值后,才能使用它

赋值操作的约束

int型变量赋值给指针式非法的,尽管这个int型变量的值可能是0,允许把数值0或在编译时可获得0值的const量赋值给指针

1
2
3
4
5
6
7
int ival;
int zero = 0;
const int c_ival = 0;
int *pi = ival;(×)
pi = zero;(×)
pi = c_ival;
pi = 0;

从C继承下来的预处理变量:

1
int *pi = NULL;

void* 指针

C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:

1
2
3
4
double obj = 3.14;
double *pd = &obj;
void *pv = &obj;
pv = pd;

void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。

不允许使用void* 指针操纵它所指向的对象。

指针操作

指针和引用的比较

第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。

指针和const限定符

介绍了指针和 const 限定符之间的两种交互类型:指向 const对象的指针和 const 指针。

指向const 对象的指针

我们使用指针来修改其所指对象的值。但是如果指针指向const 对象,则不允许用指针来改变其所指的 const 值。

为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:

1
const double *cptr;

这里的 cptr 是一个指向 double 类型 const 对象的指针,const 限定了cptr 指针所指向的对象类型,而并非 cptr 本身。也就是说,cptr 本身并不是const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值:

不能使用 void* 指针保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:

1
2
3
const int universe = 42;
const void *cpv = &universe; // ok
void *pv = &universe; // error

允许把非 const 对象的地址赋给指向 const 对象的指针,例如:

1
2
double dval = 3.14;
cptr = &dval;

但是同样必须遵循不能通过cptr修改对象的值

const指针

const指针指的是指针本身的值不能改变

指向const对象的const指针

1
2
const double pi = 3.14159;
const double *const pi_ptr = π

既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向。

指针和 typedef (讨论)

下面是一个几乎所有人刚开始时都会答错的问题。假设给出以下语句:

1
2
typedef string *pstring;
const pstring cstr;

请问 cstr 变量是什么类型?

简单的回答是 const pstring 类型的指针。

进一步问:const pstring 指针所表示的真实类型是什么?很多人都认为真正的类型是:

1
const string *cstr;

也就是说,const pstring 是一种指针,指向 string 类型的 const 对象,但这是错误的。

错误的原因在于将 typedef 当做文本扩展了。声明 const pstring 时,const 修饰的是 pstring 的类型,这是一个指针。因此,该声明语句应该是把cstr 定义为指向 string 类型对象的 const 指针,这个定义等价于:

1
string *const cstr;

创建动态数组

每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。

const 对象的动态数组

如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化:

1
2
const int *pci_bad = new const int[100];(×)
const int *pci_ok = new const int[100]();(√)

C++ 允许定义类类型的 const 数组,但该类类型必须提供默认构造函数:

1
const string *pcs = new const string[100];

已创建的常量元素不允许修改――因此这样的数组实际上用处不大。

第五章 表达式

箭头操作符

1
-> 相当于 (*sp).

const对象的动态分配和回收

1
const int *pci = new const int (1024);

与其他常量一样,动态创建的const对象必须在创建时初始化,并且一经初始化,其值就不能修改。

对于类类型的const动态对象,如果该类提供了默认构造函数,则此类可以隐身初始化,但是内置类型对象以及未提供默认构造函数的类类型对象必须显示初始化。

有符号与无符号类型之间的转换

如果int类型足够表示所有的unsigned short型的值,则将unsigned short转换为int,否则将这两个操作数转换为 unsigned int.

同理 unsigned intlong转换为unsigned long.