👾1.1 如何撰写一个C++程序
c++程序一般从一个 main()
函数开始;
例如
#include<iostream> //头文件,包含class的声明
#include<string>
using namespace std; //命名空间
int main() //int:整形数据类型
{ //函数用{……}来表示其范围
string user_name; //定义了一个对象
cout<<"Please enter your name";
cin>>user_name;
cout<<"Hello,"
<<user_name
<<"and goodbye";
return 0; //函数应有返回值;
}
函数
函数是一块独立的程序代码序列(code sequence),能够执行一些运算;包含四个部分:
- 返回值类型(return type)
- 函数名称;
- 参数列表(parameter list)
- 函数体
main()
并非程序语言中的关键字,执行程序时会假定已经定义了 main()
。
👾类class
定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作
所谓类(class),是用户自定义的数据类型。
class机制赋予了我们增加程序内之类型抽象化层次的能力;
class的定义一般分两部分,分别写在不同的文件中。
- 头文件(header file):声明该class所提供的各种操作行为(operation)
- 程序代码文件(program text):包含这些操作行为的实现内容(implementation)
命名空间
所谓命名空间(namespace)是一种将库名称封装起来的方法。通过这种方法可以避免和应用程序发生命名冲突的问题。
假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们父母的名字等等。 同样的情况也出现在 C++ 应用程序中。例如,您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。 因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。 我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
小结
- C++程序主要由函数、运算符、对象组成
- 对象可以是常量,也可以是变量
- 类的机制使得我们可以定义自己需要的对象
🦋1.2 对象的定义与初始化
为了定义对象,需要的:
- 命名
- 赋予数据类型
✨命名规则
:::info C++ 标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。习惯**将类内的成员变量以下划线开头
C++ 标识符内不允许出现标点字符,比如 @、& 和 %。C++ 是区分大小写的编程语言。因此,在 C++ 中,Manpower 和 manpower** 是两个不同的标识符。 ::: 且不允许为已经定义的系统关键字
单一语句中可以一并定义多个对象,以“,”分隔,也可以:int num_1=0, num_2=1;
初始化方法
用assignment运算符=进行初始化 string seqence_name="Fibonacci";
构造函数语法: int num_tries(0);
*内置数据类型
内置的基本数据类型
类型 | 关键字 |
---|---|
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
其实 wchar_t 是这样来的: typedef short int wchar_t
所以 wchar_t 实际上的空间是和 short int 一样。
一些基本类型可以使用一个或多个类型修饰符进行修饰:
- signed
- unsigned
- short
- long
const关键字
声明为const的变量在程序执行过程中不应该有变动,在获得初值后无法再有任何变动;如果企图改变const对象,会产生编译错误。
**
小结
- 对象的命名必须按照一定的规则
- 对象初始化的方式有两种,assignment初始化和构造函数初始化
-
1.3 撰写表达式
本节中主要介绍了运算符,C++之中的运算符可以分成四种类型:
算术运算符
- 关系运算符
- 逻辑运算符
- 复合赋值运算符
算术运算符
算数运算符基本没有什么需要注意的,唯一需要注意的是除法和求余;对于整数进行除法时,小数点后面的部分会被舍弃。
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
— | 自减运算符,整数值减少 1 | A— 将得到 9 |
关系运算符
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真 |
逻辑运算符
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都 true,则条件为 true。 | (A && B) 为 false。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个 true,则条件为 true。 | (A || B) 为 true。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态,如果条件为 true 则逻辑非运算符将使其为 false。 | !(A && B) 为 true。 |
位运算符
运算符 | 描述 | 实例 |
---|---|---|
& | 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 | (A & B) 将得到 12,即为 0000 1100 |
| | 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 | (A | B) 将得到 61,即为 0011 1101 |
^ | 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 | (A ^ B) 将得到 49,即为 0011 0001 |
~ | 二进制补码运算符是一元运算符,具有”翻转”位效果,即0变成1,1变成0。 | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
<< | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 | A >> 2 将得到 15,即为 0000 1111 |
注意补码运算与逻辑非运算不是一个东西
杂项运算符
运算符 | 描述 |
---|---|
sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
, | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
.(点)和 ->(箭头) | 成员运算符用于引用类、结构和共用体的成员。 |
Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
& | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
* | 指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。 |
来源于菜鸟教程
1.4 条件语句和循环语句
本小结介绍了基本的几个条件语句和循环语句
- if-else
- while
- switch
不多加描述了
1.5 如何运用Array和Vector
array和vector是两种存放连续数值的容器类型,这两种容器可以按地址作为索引来调用容器中的数据。
数组为C++内置的数据类型,而vector是一个class template;
数组的声明一般如: typename arrayname[arraysize];
array的初始化
array的初始话有多种方式:
- 可以在定义时初始化:
int elem_seq[]={1,2,3}
- 也可以定义空数组然后依次初始化每个元素
Vector的初始化与array不相同,在class template中会详细解释;
值得注意的一点是数组名和下标实际上是代表的内存地址
小结
- array和vector都是存放数据的容器,既可以通过名称,又可以通过存放数据的位置来读写数据
- array大小必须是常量表达式
- 若初始化时不赋值,则数组中的值是之前该块内存中所存储的值
🐧1.6 指针带来的弹性
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。我们可以操作指针,而不再直接操作对象。
通常指针具有以下形式:type_of_object_pointed_to * name_of_pointer_object
e.g.vector<int> *pv=0;
- 在定义某特定类型的指针时,需要在类型名后加上
*
; - 如果希望取得对象所在的内存地址则应使用取地址运算符
&
; - 如要访问一个指针所指的对象,需要对该指针进行提领(dereference)操作,即
*pv
;
指针的复杂往往来源于以下几点
- 指针具有双重性质:既可以操作指针包含的内存地址,又可以操作指针所指的对象值;
- 指针可能不指向任何对象,在提领时可能会(也可能不会)出现错误;
:::tips
对于此问题可以在初始化时统一定义指针地址为0(null指针),之后再使用指针前检验;
:::
int *p=0;
if(p && *p!=1024)
*p=1024;
🤷*指针数组和指向数组的指针
指针数组就是一个数组存放的都是指针变量(也就是存放的是地址)
e.g.
const int seq_cnt = 6;
// 一个指针数组,大小为seq_cnt
// 每个指针都指向vector<int>对象
vector<int> *seq_addrs[ seq_cnt ]={
&fibnacci, &lucas, &pell,
&triangular, &square, &pentagoal
};
seq_addrs是一个在栈区,有6个元素的数组,而每一个元素又是一个指针,所以说它的6个元素各占四个字节
数组指针
例如定义了这样一个数组指针char (*pa)[4];
;pa是一个指针指向一个 char [4]
的数组,每个数组元素是一个char类型的变量。既然pa是一个指针,存放一个数组的地址,那么在我们定义一个数组时, char a[4];
数组名称就是这个数组的首地址,那么这二者有什么区别和联系呢?
a是一个长度为4的字符数组,a是这个数组的首元素首地址。既然a是地址,pa是指向数组的指针,那么能将a赋值给pa吗? :::tips 答案是不行的!因为a是数组首元素首地址,pa存放的却是数组首地址,a是char 类型,a+1,a的值会实实在在的加1,而pa是char[4]类型的,pa+1,pa则会加4,虽然数组的首地址和首元素首地址的值相同,但是两者操作不同,所以类型不匹配不能直接赋值,但是可以这样:pa = &a,pa相当与二维数组的行指针,现在它指向a[4]的地址。 ::: reference