前言

顺序、选择、循环,是无论任何语言都必须要实现的机制,有了这三种语句,我们才有可能实现比较复杂的逻辑。本章便是对c++语言的循环语句进行简单的介绍。

循环语句的意思就是可以重复执行某些语句。比如,我们要计算一个整型数组所有元素的和,或者我们想计算10的阶乘等,这都需要重复相同的逻辑,只不过输入的值不同罢了。

c++主要有for循环while循环do_while循环等最为常用。c++11标准增加了foreach循环使其运用更加灵活。

阅读全文 »

指针

指针一直是c/c++学习的难点。指针是用来获取变量在内存中地址的变量。

指针使用解除引用符*来声明,而变量则可以通过取地址符&获取其在内存中的地址。故指针的声明与初始化如下代码所示。其中声明了一个指针类型的变量p,它指向变量a的地址。

1
2
int a = 5;
int* p = &a; // & 取地址

在这之后,p便代表着变量a的地址。我们可以通过解除引用符*来从指针变量获取其地址上的值,代码如下。

1
cout << *p << endl;     // * 解除引用,取值

危险指针

没有指向具有一定意义内存的指针称为空指针。如指向了nullptrNULL的指针。如果使用了空指针,将有可能造成内存访问异常,获取不到正确的数据。

除了空指针外,还有一类指针更危险且隐藏极深,那便是野指针。野指针一般出现于以下几个场景。

指针变量未初始化。此可手动初始化为nullptrNULL

指针释放后之后未置空。此时也可在释放后手动初始化为nullptrNULL

指针操作超越变量作用域。注意不要返回指向栈内存空间的指针或引用。

除此之外,还要注意,数字不能直接赋值给指针,需要强制类型转换方可。如下代码所示。

1
2
int* p;
p = (int*)0xB8000000;

指针算术

指针变量+1后,并非内存地址对应的数字+1,而是增加它指向的数据类型的字节数,即指向下一个元素。如下代码所示。

1
2
3
4
5
int a[10];
cout << a << endl;
cout << &a << endl;
cout << a+1 << endl;
cout << &a + 1 << endl;

输出结果如下。

1
2
3
4
0x6afed8
0x6afed8 // 虽然在数值上一致
0x6afedc // 但+1后发现不一样,代表的元素不一样
0x6aff00

这是因为对数组取地址时,得到的是整个数组的地址,而非第一个元素的地址,即该指针的数据类型为数组类型,而非元素类型。

1
2
int (*p)[10] = &a;      // 即&a的类型为 int(*)[10]
// 此时 (*p)[0] 即为 a[0]

另外,对指针使用sizeof()得到的是指针的长度,即使指针指向一个数组。一般指针的长度为4字节。

对于数组而言,a[i]等价于*(a + i)。所以有时候会说数组的本质也还是指针运算而已。

c++中的内存存储

自动存储

在函数内部定义的常规变量、局部变量等都使用使用自动存储空间,称为自动变量。它们在函数被调用时(或所在代码块为活动代码块时)自动产生,在函数结束时(或离开代码块时)消亡。

自动变量将存储在栈空间中,执行其所在代码块时入栈,离开代码块时出栈,且先进后出(FILO)。

静态存储

在函数外定义的变量、在函数内使用static声明的静态变量、全局变量等都使用静态存储空间,称为静态变量。它们在整个程序执行期间都存在,我们会在后面第九章详细论述此种存储方式。

动态存储

除了以上存储空间外,c++还有一个自由存储空间,称为内存池,或堆空间。动态存储空间将变量的创建和删除权限交给开发人员。理论上讲,我们可以在任何地方创建变量,同时可以在任何地方删除该变量,且在该变量存在的整个周期内,它都是存储在堆空间的某个具体位置上的。

换句话数,动态数据的生命周期不完全受程序和函数的生存时间控制,我们可在一个函数中创建,在另一个函数中删除,对内存有了更大的控制权。但这也导致一些变量可能占用的自由存储空间不连续,使得跟踪内存位置更加困难。

动态内存分配

我们使用new关键字来创建存储在堆空间的动态变量,使用delete关键字来删除该动态变量。newdelete的基本使用格式如下。

1
type_name* point_name = new type_name;  // new
1
delete point_name;                      // delete

下面以int为例。

1
2
3
4
5
6
int* p = new int;                       // new

// 一些操作
*p = 10;

delete p; // delete

需要注意的是,使用delete释放new申请的内存时,并不会删除指针本身,还是可以重新分配内存给该指针。与此同时,newdelete要配对使用,否则会发生内存泄漏(memory leak),导致堆空间有无法回收的内存。

更不要尝试释放已经释放的内存块,其结果将不确定,故不要创建两个指向同一内存块的指针。但是对空指针delete是安全的,所以我们最好在delete之后加上一句置空的代码。

1
2
3
int* p = new int;
delete p;
p = nullptr; // 或 p = NULL;

动态复合类型

使用new也可以创建动态数组。其一般格式如下。

1
type_name* point_name = new type_name[element_num];     // new
1
delete[] point_name;                                    // delete

同样以int为例。

1
2
3
4
5
6
7
int* p = new int[10];                                   // new

// 一些操作
*p = 10;
p[1] = 20;

delete[] p; // delete

其中数组的第一个元素为*p,也是p[0],数组下标为i个元素为p[i],参见上述指针算术。

使用new也可创建结构体。此时不再使用.进行成员访问,而是替换为->

1
2
3
4
5
6
7
struct pos {
int x, y;
};

pos* p = new pos;
cout << p->x << endl; // 使用->访问成员
cout << (*p).y << endl; // 也可使用*取结构体的值

数组

数组是一组数据的集合,它们有序地依次排列成一排,无论是代码中还是实际内存上。

比如我们经常吃的一串糖葫芦,就是一个数组,而糖葫芦上的每个山楂,就是这个数组的元素,它们紧密地排列成一排。

声明

1
typename array_name[array_size];
1
int a[100];

需要注意,varray_size不能是变量,而是一个整型常量。

我们可通过索引(下标)对数组元素进行访问,其中下标的范围为[0, array_size)

1
a[3] = 100;

初始化

对于一个数组而言,只有创建时才能使用初始化表初始化,此后便不能使用初始化表初始化了。除此之外,也不能直接将一个数组赋值给另一个数组,如a=b

1
2
3
int a[4] = { 3, 6, 8, 10};
int b[4] = { 1, 2, 3, 4 };
a = b; // error

使用初始化表可只对一部分元素初始化,剩下的默认为0。

1
int a[4] = {2, 3};          // 此时数组值为 2 3 0 0 

也可不在{}内包含任何东西, 此时所有元素初始化为0。

1
double b[4]{}

若[]内为空,则编译器将自动计算元素个数。

1
int a[] = {2, 3, 4, 5};     // 此时size = 4

另外,c++11标准新增了一个语法,就是初始化时可省略等于号(=)。

1
double b[4]{1, 2, 3, 4};

字符串

字符串是以空字符(ASCII码为0,c++里表示为\0)结尾的、以char类型为元素的数组。然而其初始化除了使用上面介绍的形式外,还可以使用双引号("")将所有的字符括起来,直接声明一个串,如下代码所示。

1
2
char pig[8] = { 'v', 'e', 'r', 'y', 'f', 'a', 't', '\0'};
char pig[] = "veryfat";

任何两个由空白(空格、制表符、换行符)分隔的字符串常量,都会自动拼接为一个字符串,如下代码所示。

1
cout << "aaa" "bbb" endl;   // 输出 aaabbb

输入一行字符串

当我们需要输入一个字符串时,如果直接用cin来输入,那么当它碰到中间有空格的字符串便只能读取到第一个空格所在的位置了,如输入hello world到一个字符串里的话,使用cin只能读取hello,因为识别到空格时即止。

所以如果想直接输入hello world这样中间有空格的一行数据到一个字符串里,需要用来其它函数。这里触及到的函数有两个,一个是getline(),另一个是get()

getline()将丢弃最后面的换行符\n,使用空字符替代。

1
2
cin.getline(name, size);
// 若输入为"hello world",则name = "hello world\0";

get()并不读取换行符,而是将其留存输入队列中,如果我们继续使用cin,才会接着读取到换行符。

1
2
cin.get(name, size);        // 此时换行符留在了输入队列中
cin.get(); // 可读取下一个字符即使是换行符

除此之外,get()函数返回的还是cin对象,所以可以继续调用get()函数,如下所示。

1
2
cin.get(a, maxn).get();
cin.get(b, maxn); // 返回一个cin对象,getline()也是

读取空行

get()读取空行后将设置失效位(failbit),这意味着接下来的输入将被阻断。这个时候需要使用cin.clear()来恢复输入。当输入字符串比分配的空间长时,余下的字符将会留在输入队列中,且getline()会设置失效位。

当使用cin >> a输入数字之后,会将换行符留在输入队列,此时应使用cin.get()cin.get(ch)来读取并丢弃换行符。

字符串操作

目前我们所提及的字符串都是c语言中的字符中形式,即由字符数组和\0组成的,称之为c串。c语言头文件<cstring>可以对c串进行一些基本的操作,如复制、拼接、查找子串等。

1
2
strcpy(charr1, charr2);     // 复制charr2到charr1
strcat(charr1, charr2); // 拼接charr2到charr1

原始字符串

在c++里还有一种字符串,它直接输出字面的内容,而不需要转义字符,这种字符串称为原始字符串。

我们使用R作为前缀,()作为定界符来声明原始字符串字面量。这样()里的字符串便不需要转义字符,代码里是什么其输出便会是什么。

另外我们也可以自定义定界符,而不是必须使用()来作为定界符。此时只要保证"和(之间的字符与)和"之间的字符一样即可,如下代码所示。

1
2
cout << R"(Jim "King" uses "\n" instead of endl)" << endl;
cout << R"+*(Jim "King" uses "\n" instead of endl)+*" << endl;

此代码将输出如下内容。

1
2
Jim "King" uses "\n" instead of endl
Jim "King" uses "\n" instead of endl

string类

string类是c++的字符串类,它对c串进行了封装,这在读者接触到面向对象思想之后便很容易理解了。

string类可以使用c串进行初始化,如下代码所示。

1
2
string str = "hello";
string str {"hello"};

我们可以像c串一样使用cin来读取一个string对象。同时也可以将一个string对象赋值给另一个string对象。

1
2
cin >> str;
str1 = str2;

我们可以使用加号+来拼接两个string对象。

1
2
str3 = str1 + str2;
str1 += str2; // 将str2附加到str1的尾部

我们同样可以使用getline()读取一行到string对象,但是形式上有些变化,其原因需要等到掌握了面向对象知识再做理解。

1
getline(cin, str);      // istream没有关于string的类方法

结构体

结构体struct是一种自定义类型,它可以将一些我们之前接触过的基本数据类型组合起来,形成更加复杂的类型,这种类型有可以为我们模拟当前开发任务需要的数据格式提供极大的方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义结构体
struct node {
char name[20];
float volume;
double price;
};

// 初始化
node a = { "dua", 12.5, 30.3}; // 使用初始化参数表
node b {"dra", 10.0, 11.2}; // C++11初始化结构体

// 访问结构体成员
cout << x.name << endl;

// 赋值
node c = a;

如上代码所示,我们将一个c串、一个float和一个double类型组合起来,并使用struct关键字,就声明了一种叫做node的新的数据类型。这个数据类型在一定程度上与intdouble等基本数据类型没有区别,如其声明、初始化、甚至是赋值等操作。

我们使用点运算符.来访问结构体内的具体某一个成员。

我们也可以在定义结构体的同时创建结构体变量,如下代码所示,便在定义node类型的同时声明了三个node类型的变量a b c

1
2
3
4
struct node {
char name[20];
float volume;
}a, b, c;

更进一步,与上一步同时,我们也可以初始化在定义时声明的变量。

1
2
3
4
struct node {
char name[20];
float volume;
}a, b = {"dua", 12.5}, c;

我们也可以创建无名结构体,但是我们以后再也无法声明同类型的变量了,所以只有在定义时声明我们想要的该类型的变量,且整个程序只会有这些在一开始就声明出来的该类型的变量了。

1
2
3
stuct {             // 以后无法再创建同类结构体
int x, y;
}position;

结构体数组

结构体也可以像基本数据类型一样作为数组的元素,我们可以像如下代码一样声明一个结构体数组。

1
2
3
4
5
node x[2] = {
{"dua", 12.5, 30.3},
{"dra", 13.5, 40.3}
};
cout << x[1].name << endl;

结构体中的位字段

我们可以给结构体的某一个成员指定位数以节省内存。

我们只能对结构体内的整型或枚举型(实质也是整型)和bool型(实质也是整型)的变量进行位数指定。我们在变量的名称后面跟一个冒号:,然后后面跟一个数字表示位数。如下代码所示。

1
2
3
4
5
6
7
8
9
struct node {
unsigned int SN : 4; // 4位
unsigned int : 4; // 4位
bool flag : 1; // 1位
bool ok : 1; // 1位
};

node x = {14, true, true};
if(x.flag) cout << x.SN << endl;

从上面代码也可以看出,我们可以使用没有名称的字段来提供内存间距。

共用体

共用体union也是一种自定义的数据类型,与结构体类似,其不同点在于,共用体在同一时间只能存储其成员的一种结构,主要用于节省空间。如下代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
union node {
int int_val;
long long_val;
double double_val;
};

//使用
node x;
x.int_val = 15; // 此时共用体只有int_val成员
cout << x.int_val << endl;
x.long_val = 20; // 此时共用体只有long_val成员
cout << x.long_val << endl;

这些成员的内存地址是相同的,所以共用体的长度为其最长成员的长度。

匿名共用体

1
2
3
4
5
6
7
8
9
10
11
12
13
struct widget {
char brand[20];
int type; // 负责确定哪个共用体成员是活动的
union {
long id_num; // 有的ID为数字
char id_char[20]; // 有的ID为字符串
};
};

widget x;
if(x.type == 1) cin >> x.id_num;
else cin >> x.id_char;

枚举

枚举enum是对一类整数常量的列举,它给整数赋以实际的意义,表示某个数据的状态值,增加代码的可读性。在某些场合十分有用,如颜色、星期、月份等。

1
enum color {red, green, blue};

从上面的代码也可以看出,枚举通常用来创建符号常量。其默认值从0开始,依次递增。枚举量可自动提升为int型,但int型需要强制转换成枚举型,且其值要适当。注意,枚举型没有定义加法。

我们也可以省略枚举名称来创建符号常量。

1
enum {red, green, blue};

我们可以显示地设置枚举量的值。当然也可以只显示地定义其中一些枚举量的值,而没有被赋值的枚举量的值则为其前一个枚举量+1

1
enum color {red = 1, green = 4, blue = 8, yellow};

如上代码所示,yellow的值便为9,即blue的值+1

枚举的取值范围

上限

找到枚举量的最大值,那么大于这个最大值的、最小的2的幂再减去1,即为取值范围的上限。

下限

找到枚举量的最小值,然后

如果它不小于0,则取值范围的下限即为0。 如果它小于0,则要找到比它小的、最大的2的幂再加上1,即为下限。

例如,枚举量的最大值为101,则比它大的最小的2的幂为128,减1为127,即为上限。

再例如,枚举量的最小值为-6,则比它小的最大的2的幂为-8,加1为-7,即为下限。

变量命名规则

变量由字母、数字、下划线组合而成,不能有空格。

数字不能开头。

下划线开头的一般为系统专用全局变量变量,两个下划线开头的一般给编译器使用。

字母区分大小写。

不能是c/c++语言的关键字。

长度不超过31个字符(c89)标准,c99为63个。

整型

所有用来表示整数的数据类型都称为整型。整型分为有符号整型和无符号整型。其中无符号整型前面需加unsigned关键字。

1
unsigned int a = 10;

长度

整型的长度在不同的操作系统中都有可能不同,所以c++采用了一种灵活的标准。

类型位数
short至少16位
int至少和short一样长
long至少32位, 且至少和int一样长
long long至少64位, 且至少和long一样长

例如其在各个位数的操作系统上的长度如下表所示,其中长度单位为字节(8位二进制位)。

类型16位操作系统32位操作系统64位操作系统
short222
int244
long448
long long888

最值

我们可以使用<climits>头文件获得整型的最值信息。可使用以下宏定义获得各种整型类型的大小。

1
2
3
4
SHRT_MAX SHRT_MIN
INT_MAX INT_MIN
LONG_MAX LONG_MIN
LLONG_MAX LLONG_MIN

我们使用sizeof(int)获得某个类型(这里以int型为例)的字节数。另外,对变量名使用sizeof()函数时,可以不用括号,如sizeof a

我们使用CHAR_BIT宏来获得当前操作系统一个字节占多少位,一般为8位。

选择

在所有的整型类型中,int是最自然的选择了,大部分操作系统的int都是4字节的,而不用专门进行4字节对齐(可参考操作系统相关知识了解字节对齐的好处),处理效率最高。

有时候,short也是不错的选择,如果你能保证你的数据集没有很大的数。如果此时恰好你需要存储特别多的这种不很大的数,使用short无疑会帮你节省大量的内存。

long类型的可移植性比较高,如果你要为数据将来的发展或移植考虑,如将原来在32位上跑的程序移植到64位上来,那么请直接使用long吧。

long long用来处理更大的数值,如果你打过acm竞赛就会发现,有些题目的中间结果(比如在取模之前)不用long long还真的是存不下。

有些时候我们把char类型(字符类型)也归为整型,因为它对应着[0,255]区间内的整数,它只占有一个字节,所以比short更节省内存,在某些特殊的情况下也是最好的选择。

后缀

我们使用后缀来表示一个字面量到底属于哪种类型,没有后缀的一般都为int型。如2L表示long型数值2

具体的整型后缀表如下所示。

类型后缀
longL
unsigned longuL
unsigned long longuLL

进制

一般我们显示的数值为十进制数,但是c++也可以显示八进制、十六进制的数值。

其中八进制的数据我们一般使用0作为前缀,且保证后面的每一位数不能大于7。而十六进制使用0x作为前缀,且后面每一位数可以使用0~9a~f来表示。

1
2
3
4
5
6
7
8
9
// 十进制(decimal)
int a = 23;
cout << dec << a << endl;

// 八进制(hexadecimal)
int b = 046; cout << hex << b << endl;

// 十六进制(octal)
int c = 0xAF; cout << oct << c << endl;

char

char类型为单字节字符,表示某一个字符,它将字符与其ascii码相对应。

char类型默认为unsigned char,取值范围为[0,255]。但是也可显示声明为signed char,取值范围变为[-128, 127]。

下面是一个关于char的示例程序。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main() {
char ch = 'M';
int i = ch;
cout << ch << " - " << i << endl;
cout.put(ch); cout.put('!'); // 不带回车
return 0;
}

其输出结果如下。

1
2
M - 77
M!

转义序列

我们使用转义序列来表示字符或者字符串(可以理解为由字符串起来的句子)中难以表达的字符,比如我们使用\n来表示回车。部分转义字符如下表所示。

字符含义转义序列
换行
回车
水平制表
垂直制表
退格
响铃
问号?
反斜线\\
单引号'
双引号"

c++使用反斜线\作为转义标记。可以想像一下,在c++内部解析一串字符时,当碰到\时,它便会从一张转义字符表中查找有没有与\后面的字符完全吻合的转义序列,如果有的话,则转换其意义之后再输出。这也是为什么看似稀松平常的\在字符串中出现时也需要转义成\\才能输出。

通用字符名

为了可以统一显示世界各地各种千奇百怪闻所未闻的字符,c++采用了一种通用字符名的广宁,它可以显示所有iso10646标准规定的所有unicode字符。其显示方法有以下两种。

4个十六进制位(16位),如\u006E

8个十六进制位(32位), 如\U0000222B

如下面语句。

1
cout << "k\u00E2rper" << endl;  -- k芒rper

宽字符类型

宽字符也是为了显示更多或者更完整的字符,所以一般会使用更多的空间,如2字节、4字节等。

wchar_t

wchar_t类型的字符串以L作为前缀,一般占用2个字节,且有专门的输入输出处理api,如下代码所示。

1
2
wchar_t wch = L 'P' ;           // 使用前缀L表示
wcout << L "hello" << endl; // 使用wcout和wcin代替cout和cin

char16_t和char32_t

chara16_tchar32_t都是宽类型的字符,一个占16位,一个占32位,如下代码所示。

1
2
char16_t ch1 = u'\u00F6';       // 无符号 16位
char32_t ch2 = U'\U0000222B'; // 无符号 32位

bool 型

进行逻辑运算的数据类型。

1
2
bool flag = true;               // 任何非零值
bool ok = false; // 零值

浮点型

浮点型在计算机中用来表示小数,它的存储方式(可参考计算机组成原理相关知识),存储大小,以及书写格式等与整型都存在较大的区别。

书写格式

标准小数点表示法如下所示。

1
2
0.023 
8.0

E表示法如下所示。

1
2
3
4
5
3.45E8
2.52e+5
8.33E-4
7E5
-18.34e24

有效位与取值范围

IEEE754浮点数表示法里,浮点数的存储分为三个部分,如下所示。

逐点类型总位数符号位(决定正负)指数位(决定范围)尾数位(小数部分)
float32(至少32)1823
double64(至少48,不少于float)11152
long double128(不少于double)115112

其中指数位可决定取值范围,而尾数位决定有效数字(精度)。下面以float为例详细说明。

float的指数位为8位,所以指数的范围为[-127,128],则float的取值范围为 \(\[-2^{-128},2^{128}\]\),此取值范围与正整数有关,可认为负整数无限趋近于0.0了。转换为10进制即为\(\[-3.4\times10^{38},3.4\times10^{38}\]\)

float的尾数位为23,则\(2^{23}=8388608\),总共7位,但不能保证所有的7位尾数都是精确的(大于8366608的不精确),所以float的有效数字位数为6~7位

下面为一使用浮点数的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int main() {
float a = 10.0 / 3.0;
double b = 10.0 / 3.0;
const float c = 1.0e6;

// 使用定点表示法输出
cout.setf(ios_base::fixed, ios_base::floatfield);
cout << "a = " << a << endl;
cout << "b = " << b << endl;

cout << "a*c = " << a*c << endl; // 精度不一样
cout << "b*c = " << b*c << endl;

cout << "a*c*10 = " << a*c*10 << endl; // 结果不正确
return 0;
}

输出结果如下。

1
2
3
4
5
a = 3.333333
b = 3.333333
a*c = 3333333.250000
b*c = 3333333.333333
a*c*10 = 33333332.000000

浮点后缀

浮点类型后缀示例
floatF/f1.234f 1.234F
double默认类型,无后缀1.234
long doubleL/l1.234L 1.234l

算术运算符

基本运算符

+ 加法。

- 减法。

* 乘法。

/ 除法。两个操作数都是整数时结果才为整数。

% 求模。两个操作数必须是整数。

优先级

先乘除(取模),后加减。

可用括号控制优先级。

读者可自行搜索c++运算符优先级表,后续本站也会更新优先级相关资料。

结合性

结合性意为两个优先级相同的运算符被同时用于一个操作数时,需要考虑是左结合还是右结合。

如下代码所示,会从左往右进行计算,因为对于4而言,除法/和乘法*处于同一优先级,但是左结合性的,所以从左往右计算。

1
120/4*5 = 150;

类型转换

自动类型转换

大范围转小范围(long->int或float->int等)会丢失精度。

初始化时或将一种类型赋值给另一种类型时自动转换。

表达式中包含不同类型时,会出现整型提升,即小范围的类型变为大范围的类型。

参数传递给函数时会发生自动类型转换。

以{}初始化(列表初始化)时,不允许缩窄(float->int),这是c++11的新规定。

强制类型转换

1
2
3
(typename)value;                // C语言格式
typename(value); // C++格式 -- 像函数调用一样
static_cast<typename>(value); // 使用模板函数 -- c++11提供的更严格的转换

auto初探

auto数据类型是c++11新标准定义的数据类型,它可以根据上下文语境自动推断数据类型(如果可以的话)。这在某些时候会给我们写代码时提供极大的便得,甚至实现更强大的功能。

最常见的例子是使用STL(标准模板库,以后会讲到)时迭代元素更加方便,更具有泛型的思想。如下代码所示。此处只作了解,以后会仔细分析auto更详细的用法及原理。

1
2
vector<int> x;
vector<int>::iterator it = x.begin();

可简化为如下代码。

1
2
vector<int> x;
auto it = x.begin();

hello world

无论学习什么语言,hello world都可作为第一个程序,这寓意着一个新的开始,也说明了开发环境已经基本搭建完成。本章内容主要介绍一下hello world程序的各个含义,以及c++语言的一些基本组成部分。

1
2
3
4
5
6
7
8
9
10
// hello.cpp
/* This is my first code */

#include <iostream>
using namespace std;

int main() {
cout << "hello world" << endl;
return 0;
}
阅读全文 »

前言

《C++ Primer Plus》是本人从高三到大一拜读的第一本c++系列的大部头,苦于一直没有机会整理记录一下。好在近期无事,记录一下主要知识点,仅供入门使用。后续会继续更新关于c++的更进一步的知识及实际使用技巧。

阅读全文 »

我的编程习惯

  • __尽量使用下划线(_)而非大写字母__

    • 类首字母大写
    • 类成员变量 -- 加 m_ 前缀
    • 静态成员变量 -- 加 s_ 前缀
    • 类成员函数
      • 公有函数 -- 不加任何前缀
      • 私有函数 -- 加 pri_ 前缀
      • 保护函数 -- 加 pro_ 前缀
      • 静态函数 -- 加 sta_ 前缀
      • 声明顺序
        • 先声明构造和析构相关的函数
        • 再声明对成员变量纯存取的函数
        • 最后声明其他功能函数
    • 访问控制符按 private - protected - public 方式书写

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
#include <iostream>
using namespace std;

class Example {
private:
int m_value;
int* mp_value;

void pri_add() { ++m_value; }

protected:
void pro_sub() { --m_value; }

public:
static int s_count;

Example(){}
~Example(){}

void set_value(int v) { m_value = v; }
int get_value() { return m_value; }

static int sta_get_count() { return s_count; }

void show() { cout << m_value << endl; pri_add();}

};

int Example::s_count = 0;

int main() {

cout << Example::sta_get_count() << endl;
Example x;
x.set_value(10);
x.show();
x.show();



return 0;
}

文本I/O初探

  • 输出到文件

    • 包含 < fstream > 头文件
    • < fstream > 定义了一个用于处理输出的 ofstream 类
    • 需要自己声明一个或多个ostream类对象
      • 使用此对象打开(.open()), 关闭(.close()), 输出(<<) 文本到文件
    • Example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #include <iostream>
      #include <fstream>
      using namespace std;

      int main() {

      ofstream fout; // 声明此类对象
      fout.open("out.txt"); // 打开文件
      fout << "hello world\n"; // 输入
      fout.close(); // 关闭文件

      return 0;
      }

  • 从文件中读取

    • 包含 < fstream > 头文件
    • 需要声明一个或多个 ifstream 类型的对象
      • 使用此对象进行打开(.open()) , 关闭(.close()) 和 读取( >> .get() 或 getline()等)
    • 需要正确设计读取循环,以读取到正确的内容
      • 遇到EOF时, 方法 eof() 将返回true
      • 遇到EOF 或 类型不匹配时, 方法 fail() 将返回true
      • 最后一次读取文件时发生文件受损或硬件错误,方法 bad() 将返回true
      • 没有发生任何错误时, 方法 good() 将返回 true -- 对于上一次输入而言 (一般在前面有一条输入)
    • Example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      #include <iostream>
      #include <fstream>
      #include <cassert>
      using namespace std;

      int main() {

      ifstream fin;
      fin.open("in.txt");
      if(!fin.is_open()) { assert(0); } // 检测是否成功打开

      int x; fin >> x; // 从文件中读取
      cout << x << endl;

      fin.close();
      return 0;

      }