C++ | 核心知识及语法

1
2
3
4
5
6
int main()
{
cout << "Hello World"; //c-out 输出,相当于printf
cout << "I am learning C++" << endl; //可以同时输出多个,endl是换行
return 0;
}

数据类型

缩写 类型 备注
int 整形
bool 布尔型
char 字符型
float 浮点型
double 双浮点型
void 无类型
wchar_t 宽字符型
string 字符串 不是标准类型,需要引用头文件 <string>

输入、输出

C语言和C++输入输出总结

标准输入流

C语言

C语言使用标准输入输出函数,需要包含头文件<stdio.h>。在 C++ 中,只要包含头文件<iostream>,就完全可以使用这些 C 中的输入输出函数。

输入流
stdin是一个文件描述符(Linux)或句柄(Windows),它在 C 程序启动时就被默认分配好。在 Linux 中一切皆文件,stdin也相当于一个可读文件,它对应着键盘设备的输入。因为它不断地被输入,又不断地被读取,像流水一样,因此通常称作输入流。
stdin是一种行缓冲I/O。当在键盘上键入字符时,它们首先被存放在键盘设备自身的缓存中(属于键盘硬件设备的一部分)。只有输入换行符时,操作系统才会进行同步,将键盘缓存中的数据读入到stdin的输入缓冲区(存在于内存中)。所有从stdin读取数据的输入流,都是从内存中的输入缓冲区读入数据。当输入缓冲区为空时,函数将被阻塞。
若无特殊说明,以下所有的 缓冲区均是指内存中的stdin输入缓冲区。用户程序中自定义的buffer数组、str数组等,将称作“数组”、“变量”,以免产生混淆。

scanf()
按照特定格式从stdin读取输入。

1
2
3
char str[100];
int a;
scanf("%s %d", str, &a); // 注意,传入的一定是变量的地址

传入的一定要是地址。特别的,对于上面例子里的char str[],这是一个字符串数组,数组名 str 或者指针 &str 都可以代表其地址。

对空白字符的处理:

  • 缓冲区开头:丢弃空白字符(包括空格、Tab、换行符),直到第一个非空白字符才认为是第一个数据的开始。
  • 缓冲区中间:开始读取第一个数据后,一旦遇到空白字符(非换行符), 就认为读取完毕一次。遇到的空白字符残留在缓冲区,直到下一次被读取或刷新。例如输入字符串this is test,则会被认为是3个字符串。
  • 缓冲区末尾:按下回车键时,换行符\n残留在缓冲区。换行符之前的空格可以认为是中间的空白字符,处理同上。

C++

cin

1
2
3
4
int a;
char str[20];
cin >> a;
cin >> str;
  • 属于命名空间 std
  • 遇到空白字符停止读取,空白字符(包括换行符)残留在缓冲区

cin.get()
读取一个或指定长度的字符,包括空白字符,可指定分隔符类型,分隔符默认是(换行符)残留在缓冲区。

1
2
3
4
char a;
char str[20];
a = cin.get(); //读取一个字符
cin.get(str, sizeof(str), '\n'); //读取字符串长度的字符数,不包括终止字符(默认是换行符)

cin.getline()
读取指定长度的字符,包括空白字符


cin.getline()cin.get()指定读取长度时的用法几乎一样。区别在于,如果输入的字符个数大于指定的最大长度n-1(不含终止符),cin.get()会使余下字符残留在缓冲区,等待下次读取;而cin.getline()会给输入流设为 Fail 状态,在主动恢复之前,无法再进行正常输入。


getline()
不是标准输入流iostream的函数,而是字符串流的函数,使用时需要包含头文件<string>

如果使 用getline() 读取标准输入流的数据,需要显式指定输入流。

1
2
string str;
getline(cin, str);

getline()会读取所有空白字符,且缓冲区末尾的换行符会被丢弃,不残留也不写到字符串结尾。同时,由于string对象的空间是动态分配的,所以会一次性将缓冲区读完,不存在读不完残留在缓冲区的问题。

需要注意的是,假如缓冲区开头就是换行符(比如可能是上一次cin残留的),则getline()会直接读取到空字符串并结束,不会给键盘输入的机会。所以这种情况下要注意先清除开头的换行符。


总结

  • C语言可使用
    • scanf(),读取不包括空格和终止符(默认换行符)
    • fgets()
  • C++中可使用
    • cin >>类似于scanf(),是std中的
    • cin.get()读取单个或指定长度字符,读取包括空格,不包括终止符(默认换行符)
    • cin.getline()读取指定长度的字符,读取包括空格,不包括终止符,且读取完成后会把输入流设定为Fail状态。可用于char str[100]这样的char类型的字符串整行输入
    • getline 读取所有空白字符(整个缓存区),不包括换行符,读取完后无残留。是字符串流的函数,只能读取string对象,使用时需要包含头文件<stirng>。可用于string s这样的的string类型的字符串的整行输入

字符串

  • 一个带空格的句子的存储办法可以是str[100]这样的指定长度的字符串(C语言风格)
  • 另一种办法是用封装好的字符串类string(C++风格),动态分配长度
    • 可以进行赋值、连接、获取长度等操作

标准输出流

C语言

C++

运算符

常量与变量

1
const int myVar  //const常量

变量命名的一般规则是:

  • 名称可以包含字母、数字和下划线
  • 名称必须以字母或下划线(_)开头
  • 名称要区分大小写,myVar和myvar是不同的变量
  • 名称不能包含空格或特殊字符,如#%等。
  • 保留字(如C++关键字,如int不能作为名称使用)
1
2
3
4
5
6
7
8
int main()
{
int x;
cout << "Type a number: ";
cin >> x;
cout << "Your number is: " << x;
return 0;
}

字符串

字符串

  • 一个带空格的句子的存储办法可以是str[100]这样的指定长度的字符串(C语言风格)
  • 另一种办法是用封装好的字符串类string(C++风格),动态分配长度
    • 可以进行赋值、连接、获取长度等操作

程序流程结构

选择语句

循环语句

跳转语句

数组

string cars[4] = {"BYD", "Ford", "Mazda", "BWM"}

下标从0开始

指针

取地址运算符 &
& 是一元运算符,返回操作数的内存地址。例如,如果 var 是一个整型变量,则 &var 是它的地址。该运算符与其他一元运算符具有相同的优先级,在运算时它是从右向左顺序进行的。

您可以把 & 运算符读作"取地址运算符",这意味着,&var 读作"var 的地址"。

间接寻址运算符 *
第二个运算符是间接寻址运算符 *,它是 & 运算符的补充。* 是一元运算符,返回操作数所指定地址的变量的值。

请看下面的实例,理解这两种运算符的用法。

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

int main()
{
string food = "Pizza";
string &meal = food; //取meal的地址指向food的内容\值,类似于快捷方式引用,使得他们都是Pizza

cout << food << "\n"; //输出Pizza
cout << meal << "\n"; //输出Pizza
cout << &food; //输出地址

return 0;
}
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()
{
string food = "Pizza";
string *ptr = &food; //ptr里面的值是一个内存地址

cout << food << "\n"; //输出Pizza
cout << &food << "\n"; //输出地址

cout << ptr << "\n"; //输出地址
cout << &ptr << "\n"; //输出地址对应的值

*ptr = "Hamburger"; //对地址所在的值赋值,即*ptr等效于food
cout << *ptr << "\n";

return 0;
}

函数

函数的引用

函数的调用便于功能的复用,例如可以编写一个打印函数,通过传参使之实现打印功能

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

void myFuction()
{
cout << "666";
}

int main()
{
myFuction(); //函数引用
return 0;
}

函数的声明

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

void myFuction();//必须要在最开始先声明函数,函数的实现可以不一定放在最开始

\\主函数放在最后,也就是其他函数需要提前声明
int main()
{
myFuction(); //函数引用
return 0;
}

void myFuction()
{
cout << "666";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

//可以设置默认参数,假如引用函数时没有传参,就会使用默认参数
void myFuction(string fname = "Jack"){
cout << fname << "Refsnes\n";
}

int main()
{
myFuction("Liam"); //函数引用,分别传入三个不同的参数
myFuction("Jenny");
myFuction("Anja");
myFuction();
return 0;
}

函数亦可以设置返回值,调用函数的最终结果就是获得了返回值

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

//可以设置默认参数,假如引用函数时没有传参,就会使用默认参数
void myFuction(int x, int y)
{
return x + y;
}

int main()
{
cout << myFuction(5, 3);
int z = myFuction(5, 3);
cout << z << endl;
return 0;
}

交换函数的实现

  • 传递值进函数内交换
  • 传递指针进函数内交换
  • 用引用也就是别名交换
  • 用定义宏的方式交换
  • 自带的交换函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方式一:传指针
template<class T>
void swap1(T *p1,T *p2){
T temp = *p1; //temp的值为p1所指向的值
*p1 = *p2; //*p1的值为p2所指向的值
*p2 = temp; //*p2的值为temp所指向的值,即为temp所指向的值p1
}
//测试
int main(){
int a=10;
int b=-23;
swap1(&a,&b); //这就是调用方法的示例
cout<<a<<","<<b;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//方法二,用引用进行交换
template<class T>
void swaps(T &a,T &b){
T temp = a;
a = b;
b = temp;
}
int main(){
int a=-2;
int b=16;
swaps(a,b);
cout<<a<<endl<<b; //现在a为16,b为-2
return 0;
}
1
2
3
4
5
6
7
8
9
//方法三,定义宏
#define SWAP2(a,b,temp)((temp)=(a),(a)=(b),(b)=(temp)) //定义宏
int main(){
int a=15;
int b=-56
int temp;
SWAP2(a,b,temp);
cout<<a<<";"<<b<<endl; //此时的结果为-56;15
}
1
2
3
4
5
6
7
8
//方法四,用自带函数
int main(){
int a,b;
a = 100;
b = -200;
std::swap(a,b);
cout<<a<<endl<<b; //此时b=100;a=-200
}

函数重载

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

//可以设置默认参数,假如引用函数时没有传参,就会使用默认参数
int myFuction(int x, int y)
{
return x + y;
}

double myFuction(int x, int y)
{
return x - y;
}

int main()
{
cout << "Int: " << myFuction(5, 3);
cout << "Double: " << myFuction(5, 3);

return 0;
}

结构体

类和多态

类时对象的模板,对象是类的一个实例。当某个对象被创建的时候,他就继承了类里的所有特性。

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

class Car
{
public:
string brand;
string model;
int year;
};

int main()
{
Car carObj1; // 创造了类的对象(实例),只有在创建实例的时候才真正新生成一个内存块,使用了内存空间
carObj1.brand = "BWM"; // 给这个对象的brand属性赋值
carObj1.model = "BWM";
carObj1.year = 1997;

cout << carObj1.brand << " " << carObj1.model << " " << carObj1.year; //输出实例的三个属性,用空格作为间隔
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
#include <iostream>
#include <string>
using namespace std;

class Car
{
public:
int speed(int maxSpeed);
void myMethod()
{
cout << "Hello World!";
}
};

//方法speed的实现
int Car::speed(int maxSpeed)
{
return maxSpeed;
}

int main()
{
Car myObj; // 创造了类的对象(实例)
cout << myObj.speed(200) << endl;
myObj.myMethod(); //调用方法(类中的函数)
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
#include <iostream>
#include <string>
using namespace std;

class Car
{
public:
string brand;
string model;
int year;
Car(string x, string y, int z);
};

// 构造函数
Car::Car(string x, string y, int z)
{
brand = x;
model = y;
year = z;
}

int main()
{
Car carObj1("BWM", "XS", 1999); // 直接传入三个参数
cout << carObj1.brand << " " << carObj1.model << " " << carObj1.year; // 输出实例的三个属性,用空格作为间隔
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
#include <iostream>
using namespace std;

class MyClass
{
int b;

public:
int x;
int y;

private:
int a;
};

int main()
{
MyClass myObj;
myObj.x = 25; // public的x可以访问
myObj.a = 50; //private的a不能访问,是私有属性
myObj.b = 11;
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
#include <iostream>
using namespace std;

class Employee
{
private:
int salary;

public:
void setSalary(int s)
{
salary = s;
}
int getSalary()
{
return salary;
}
};

int main()
{
Employee myObj;
myObj.setSalary(5000);
cout << myObj.getSalary();
}

类的继承

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

// 父类
class Vehicle
{
public:
string brand = "Ford";
void honk()
{
cout << "Tuut, tuut! \n";
}
};

// 子类,继承父类的属性(特性brand,方法honk)
class Car : public Vehicle
{
public:
string model = "Mustang";
};

int main()
{
Car myCar; // 创建实例myCar
myCar.honk();
cout << myCar.brand + " " + myCar.model;
return 0;
}

子类可以继承多个父类的特性(多继承)。

父类里的prtected只能被子类访问

多态性

继承让子类能从父类中继承属性和方法,多态性使用这些方法来执行不同的任务,即可以让我们用不同的方法执行一个单一的动作。

例如,有一个叫Animal的基类,他有一个叫做animalSound()的方法。动物的派生类可以是牛马猫狗,而且他们也可以有自己动物声音的不同实现(牛叫、马叫、猫叫、狗叫等)。方法名称相同,但调用的都是自己的方法。

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

// 基类
class Animal
{
public:
void animalSound()
{
cout << "The animal makes a sound";
}
};

// 派生类
class Pig : public Animal
{
public:
void animalSound()
{
cout << "The pig says: wee, wee \n";
}
};

// 派生类
class Dog : public Animal
{
public:
void animalSound()
{
cout << "The pig says: bow, bow \n";
}
};

int main()
{
Animal myAnimal;
Pig myPig;
Dog myDog;

myAnimal.animalSound();
myDog.animalSound();
myPig.animalSound();
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
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
// 创建txt
ofstream MyFile("filename.txt");
// 写入内容
MyFile << "Flies can be tricky, but it is fun enough!";
// 关闭文件
MyFile.close();

string myText; // 创建一个字符串,用于之后输出文件内容
ifstream MyReadFile("filename.txt"); // 读取文件

// 创建一个循环,用于输出一行一行读取的文件内容
while (getline(MyReadFile, myText))
{
cout << myText;
}
MyReadFile.close();
}

异常处理