1 基本结构
//导入系统输入输出头文件 iostream
#include <iostream>
//使用标准命名空间 std
using namespace std;
int main() {
// TODO 将Hello, World!打印在屏幕上,并且进行换行
cout << "Hello, World!" << endl;
return 0;
}
1、程序的第一行#include 是预处理器指令,告诉 C++ 编译器在实际编译之前要包含iostream 文件;
2、using namespace std表示我们可以使用标准库中对象和变量的名称;
3、int main()是主函数,程序从这里开始执行;
4、/…/将会被编译器忽略,这里放置程序的注释内容。它们被称为程序的注释;
5、cout << “Hello, World!” << endl,将Hello, World!打印在屏幕上,并且进行换行;
6、return 0;终止 main() 函数,并返回值 0;
2 变量
变量指的是会随着程序运算而改变的量,维护这些变量会用到计算机的存储功能。
- 计算机的存储功能会使用内存实现。
- 计算机中的操作系统一般会把内存划分成不同区域来存储数据,以便于管理。
- 内存中每个基本存储单元可以存放一个字节的数据,每个字节具有8位,也就是8个比特(bit)。
- 每个内存单元有一个唯一的地址,常用一个16进制数表示和区分。
- 变量的声明就是向内存申请变量,用于存放数据的过程,一般的声明方式为
数据类型
变量名称
。
// 声明可乐数量变量 coke
int coke;
// 声明爆米花数量变量 popcorn
int popcorn;
// 声明消费总额变量 money
int money
变量名称也叫做标识符,有固定的构造规则:
- 只能由字⺟、数字和下划线组成。
- 数字不可以出现在第一个位置上。
- C++的关键字(保留字)不可以⽤做标识符。
- 最好简单易懂,用具有对应含义的英文或者拼音来表示。
- 在声明变量时,也可以赋予这个变量一个初值,这被称为变量的初始化。
3 循环结构
当需要重复执行一段代码很多遍的时候,可以使用循环结构来解决问题。
- 循环结构是指在程序中需要反复执行某个功能而设置的一种程序结构。
- 由循环体中的条件,判断继续执行该功能还是退出循环。
3.1 while循环
只要给定的条件为真,while 循环语句会重复执行一个目标语句。
while(condition)
{
statement(s);
}
3.2 for循环
多次执行一个语句序列,简化管理循环变量的代码。
for (循环变量赋初始值;循环条件;更新循环变量)
{
循环体
}
3.3 do…while 循环
不像 for 和 while 循环,它们是在循环头部测试循环条件。do…while 循环是在循环的尾部检查它的条件。do…while 循环与 while 循环类似,但是 do…while 循环会确保至少执行一次循环。
do
{
statement(s);
}while( condition );
3.4 嵌套循环
一个循环内可以嵌套另一个循环。C++ 允许至少 256 个嵌套层次。
for ( init; condition; increment )
{
for ( init; condition; increment )
{
statement(s);
}
statement(s); // 可以放置更多的语句
}
4 字符串
C++ 提供了以下两种类型的字符串表示形式:
- C 风格字符串
- C++ 引入的 string 类类型
4.1 C 风格字符串
C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。字符串实际上是使用 null 字符 \0 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
#include <iostream>
#include <cstring>
using namespace std;
int main ()
{
char str1[13] = "runoob";
char str2[13] = "google";
char str3[13];
int len ;
// 复制 str1 到 str3
strcpy( str3, str1);
cout << "strcpy( str3, str1) : " << str3 << endl;
// 连接 str1 和 str2
strcat( str1, str2);
cout << "strcat( str1, str2): " << str1 << endl;
// 连接后,str1 的总长度
len = strlen(str1);
cout << "strlen(str1) : " << len << endl;
return 0;
}
4.2 string 类类型
C++提供了一套更好的工具,叫做标准模版库(Standard Template Library,STL),封装了很多功能,比如一些数据结构(队列等)和算法(排序等),其中一种叫 string 的数据类型可以专门用来处理字符串的相关问题。string 类型直接把字符串当作一个整体,可以像整型变量、字符变量一样使用。
#include <iostream>
// 加载string 头文件
#include <string>
using namespace std;
int main() {
string userName_old = "LaoWang";
string userName_new = "Liu";
string str;
int len;
// 连接小六的新老用户名,并赋值给str
str = userName_old + userName_new;
cout << userName_old << " + " << userName_new << " = " << str << endl;
// 输出连接后str的总长度
cout << "str length: " << str.size() << endl;
// cout << "str length: " << str.length() << endl;
// 比较新老用户名的字典序
cout << (userName_old > userName_new) << endl;
// 再把老用户名赋给str
str = userName_old;
cout << "str : " << str << endl;
return 0;
}
string还有一些非常方便的操作函数
5 结构化程序设计
5.1 结构化程序设计
自顶向下的分解,自底向上的实现。每个小问题设计为一个函数,公共小问题可以设计成一个库
程序由3种基本结构组成
- 顺序
- 分支
- 循环
5.2 枚举类型与头文件
5.2.1 枚举类型
枚举类型(enumeration)是 C++ 中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
enum <类型名> {<枚举常量表>};
enum color_set1 {RED, BLUE, WHITE, BLACK}; // 定义枚举类型color_set1
枚举常量代表该枚举类型的变量可能取的值,编译系统为每个枚举常量指定一个整数值,默认状态下,这个整数就是所列举元素的序号,序号从0开始。可以在定义枚举类型时为部分或全部枚举常量指定整数值,在指定值之前的枚举常量仍按默认方式取值,而指定值之后的枚举常量按依次加1的原则取值。 各枚举常量的值可以重复。例如:
enum fruit_set {apple, orange, banana=1, peach, grape}
//枚举常量apple=0,orange=1, banana=1,peach=2,grape=3。
enum week {Sun=7, Mon=1, Tue, Wed, Thu, Fri, Sat};
//枚举常量Sun,Mon,Tue,Wed,Thu,Fri,Sat的值分别为7、1、2、3、4、5、6。
枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。
枚举类型与枚举变量可以同时定义,枚举变量的值只能取枚举常量表中所列的值,就是整型数的一个子集。枚举变量占用内存的大小与整型数相同。
enum {Sun,Mon,Tue,Wed,Thu,Fri,Sat} weekday1, weekday2;
weekday1 = Sun;
weekday2 = Mon;
枚举变量允许的关系运算有:==、<、>、<=、>=、!=等
//比较同类型枚举变量color3,color4是否相等
if (color3==color4) cout<<"相等";
//输出的是变量color3与WHITE的比较结果,结果为1
cout<< color3<WHITE;
5.2.2 头文件
通常,在一个 C++ 程序中,只包含两类文件.cpp 文件和 .h 文件。其中.cpp 文件被称作 C++ 源文件,里面放的都是 C++ 的源代码;而 .h 文件则被称作 C++ 头文件,里面放的也是 C++ 的源代码。
所谓的头文件,其实它的内容跟 .cpp 文件中的内容是一样的,都是 C++ 的源代码。但头文件不用被编译。我们把所有的函数声明全部放进一个头文件中,当某一个 .cpp 源文件需要它们时,它们就可以通过一个宏命令 “#include” 包含进这个 .cpp 文件中,从而把它们的内容合并到 .cpp 文件中去。当 .cpp 文件被编译时,这些被包含进去的 .h 文件的作用便发挥了。
举一个例子,假设所有的数学函数只有两个f1 和 f2,那么我们把它们的定义放在 math.cpp 里:
/* math.cpp */
double f1()
{
//do something here....
return;
}
double f2(double a)
{
//do something here...
return a * a;
}
/* end of math.cpp */
并把"这些"函数的声明放在一个头文件 math.h 中
/* math.h */
double f1();
double f2(double);
/* end of math.h */
在另一个文件main.cpp中,我要调用这两个函数,那么就只需要把头文件包含进来:
/* main.cpp */
#include "math.h"
main()
{
int number1 = f1();
int number2 = f2(number1);
}
/* end of main.cpp */
.h 文件不用写在编译器的命令之后,但它必须要在编译器找得到的地方(比如跟 main.cpp 在一个目录下)main.cpp 和 math.cpp 都可以分别通过编译,生成 main.o 和 math.o,然后再把这两个目标文件进行链接,程序就可以运行了。
#include 的作用是把它后面所写的那个文件的内容,完完整整地、一字不改地包含到当前的文件中来。
头文件中应该只放变量和函数的声明,而不能放它们的定义。因为一个头文件的内容实际上是会被引入到多个不同的 .cpp 文件中的,并且它们都会被编译。放声明当然没事,如果放了定义,那么也就相当于在多个文件中出现了对于一个符号(变量或函数)的定义,纵然这些定义都是相同的,但对于编译器来说,这样做不合法。
设想一下,如果 a.h 中含有类 A 的定义,b.h 中含有类 B 的定义,由于类B的定义依赖了类 A,所以 b.h 中也 #include了a.h。现在有一个源文件,它同时用到了类A和类B,于是程序员在这个源文件中既把 a.h 包含进来了,也把 b.h 包含进来了。这时,问题就来了:类A的定义在这个源文件中出现了两次!于是整个程序就不能通过编译了。
使用 “#define” 配合条件编译可以很好地解决这个问题。在一个头文件中,通过 #define 定义一个名字,并且通过条件编译 #ifndef…#endif 使得编译器可以根据这个名字是否被定义,再决定要不要继续编译该头文中后续的内容。
#ifndef _name_h
#define _name_h
头文件真正需要写的内容
#endif
// 文件:p_r_s.h
// 本文件定义了两个枚举类型,声明了本程序包括的所有函数原型
#ifndef P_R_S
#define P_R_S
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
enum p_r_s
{
paper,
rock,
scissor,
game,
help,
quit
};
enum outcome
{
win,
lose,
tie
};
outcome compare(p_r_s player_choice, p_r_s machine_choice);
void prn_game_status();
void prn_help();
void report(outcome result);
p_r_s selection_by_machine();
p_r_s selection_by_player();
#endif
5.3 库与预处理
库:常用的工具
库的主题:同一个库中的函数都应该是处理同一类问题,自己设计的库也要有一个主题
库的通用性:在某一应用程序中提取库内容时应尽量考虑到兼容更多的应用,使其他应用程序也能共享这个库
设计库的接口:库的用户必须了解的内容,包括库中函数的原型、这些函数用到的符号常量和自定义类型,接口表现为一个头文件
设计库中的函数的实现:表现为一个源文件
例如:生成low到high之间随机数的库
- 随机函数库接口文件
//文件:Random.h
//随机函数库的头文件
#ifndef _random_h
#define _random_h
//函数:RandomInit
//用法:RandomInit()
//作用:此函数初始化随机数种子
void RandomInit();
//函数:RandomInteger
//用法:n = RandomInteger(low, high)
//作用:此函数返回一个 low 到 high 之间的随机数,包括 low 和 high
int RandomInteger(int low, int high);
#endif
- 随机函数库实现文件
//文件:Random.cpp
//该文件实现了Random库
#include <cstdlib>
#include <ctime>
#include "Random.h"
//函数:RandomInit
//该函数取当前系统时间作为随机数发生器的种子
void RandomInit()
{
srand(time(NULL));
}
// 函数:RandomInteger
// 该函数将0到RAND_MAX的区间的划分成high - low + 1 个 子区间。当产生的随机数落在第一个
// 子区间时,则映射成low。 当落在最后一个子区间时,映射成high。当落在第 i 个子区间时
//(i 从 0 到 high-low),则映射到low + i
int RandomInteger(int low, int high)
{
return (low + (high - low + 1) * rand() / (RAND_MAX + 1));
}
6 指针和引用
6.1 指针基本运算
指针基本概念
指针保存了一个内存地址,对指针的操作就是对地址的操作。可以将内存理解为一个“大数组”,指针相当于存储了一个数组下标,它指向下标对应位置的变量。
用以下代码声明指针。其中,&
运算符被称为取地址运算符,它返回变量在内存中的地址。void指针可以指向任意类型的值
int a;
int *ptrToA = &a;
double b;
double *ptrToB = &b;
void *voidPtrToA = &a;
void *voidPtrToB = &b;
取地址运算符不能作用于常量或表达式,因为他们在内存中并没有固定的地址。
指针赋值
使用指针时,假设 ptr 为一个指针。
- ptr 的值为指针本身的值,是一个十六进制地址;
- 同类型的指针之间可以赋值,如 ptr1 = ptr2,赋值相当于改变了指针指向的对象。
间接访问
为了访问指针指向的值,我们使用 * 符号,这被称为解引用运算符:
- *ptr 的值为指针指向的变量的值;
- 对 *ptr 的修改会作用到原对象上。
注意声明int *ptr = &a; 中的 * 并不是解引用运算符,它是类型声明的一部分。
void类型
void* 指针可以指向任何类型的值。使用 void* 代表着你放弃了所有类型检查和类型安全性。因此,除非必要,不建议在C++代码中使用 void* 类型。
int a = 233;
int *ptrToA = &a;
void *voidPtrToA = &a;
cout << *ptrToA << endl; // 233
cout << ptrToA << endl; //一个十六进制数,表示内存中的位置。例如0x7ffd99314e64
*ptrToA = 466;
cout << a << endl; // 466
*voidPtrToA = 699; // Compile Error: ‘void*’ is not a pointer-to-object type
int c = 1, *ptrToC = &c;
ptrToA = ptrToC; // 现在ptrToA指向了变量c
reinterpret_cast
reinterpret_cast,是C++里的强制类型转换符。
int x = 1;
float *fp = reinterpret_cast<float *> &x; //强行让fq指向x
转换后的类型,它的有效长度不能比原来的类型更长。比如说 int 类型为 4 byte,double 类型为 8 byte。将一个指向 int 类型的指针转换成指向 double 类型的指针,这在语法上没有问题,但是如果解引用得到的指针,double 多出的 4 byte 的数据是无意义的。
6.2 指针与数组
数组名其实就是指向数组第0个元素的指针。但是,不能修改“数组名”这个指针的值,即它是常量指针。
int a[] = {1, 2, 3};
*a; // 等同于a[0]
*(a + n); // 等同于a[n]
int *p = a + 1;
*p; // 2
*(p + 1); // 3
当指针指向数组元素时,加减法才有意义。
- 可以对指针加上或者减去一个整数。这表示:将指针在数组中向前或向后移动若干位置。
- 当两个指针指向同一个数组时,可以对两个指针做减法。这表示两个指针所指向元素在数组中的距离。
p = p + 1; // *p == 3
p = p - 1; // *p == 2
cout << p - a << endl; // 1
指针在偏移后不能超过数组的范围,也不要对不在同一个数组内的两个指针执行减法。