入门
hello world
#include <iostream> //标准输入输出流,等价于c语言的stdio.h
using namespace std; //使用标准命名空间,否则需要使用std::cout
int main() {
/**
* cout: 标准输出流对象
* <<: 在C++中,用于在cout后拼接输出的内容(字符串拼接)
* endl: end line,表示刷新缓冲区并换行
*/
cout << "hello world\n"; //默认不带换行符
cout << "hello world" << endl; //换行
cout << "hello " << "world " << 123; //字符串拼接
return EXIT_SUCCESS; //0,正常退出
}
::
#include <iostream>
int a = 1000;
int main() {
int a = 2000;
std::cout << "a = " << a << std::endl; //2000
std::cout << "a = " << ::a << std::endl; //1000,::表示作用域,如果前面什么都没有添加,则表示全局作用域
return EXIT_SUCCESS;
}
namespace
- 声明
命名空间只能在全局作用域中声明
- 同名函数
game1.h
#ifndef COURSE_GAME1_H #define COURSE_GAME1_H #include <iostream> using namespace std; namespace GAME1 { void play() { cout << "play game1" << endl; }; } #endif //COURSE_GAME1_H
game2.h
#ifndef COURSE_GAME2_H #define COURSE_GAME2_H #include <iostream> using namespace std; namespace GAME2 { void play() { cout << "play game2" << endl; }; } #endif //COURSE_GAME2_H
main.cpp
#include "ns/game1.h" #include "ns/game2.h" int main() { GAME1::play(); GAME2::play(); return 0; }
- 可存放内容
namespace GAME1 { int a = 0; //变量 void play(); //函数 struct A {}; //结构体 class B {}; //类 namespace GAME1_1 { //嵌套命名空间 int aa = 100; } };
- 可随时添加新成员
namespace A { int a = 1; } namespace A { //int a = 2; //报错 int b = 2; //命名空间是开放的,可随时添加新成员 }
- 匿名命名空间
namespace { int t = 10000; //相当于 static int t = 10000; } void test { cout << t << ::t << endl; //由于是全局作用域,可以直接调用,或者使用双冒号调用 }
- 别名
namespace aaaaaaaaa { int t = 10000; } namespace a = aaaaaaaaa; void test { cout << a::t << endl; }
using
- 声明
namespace A { int a = 1; } //声明 void declare1(){ using A::a; std::cout << a << std::endl; } void declare2(){ int a = 2; using A::a; //报错 std::cout << a << std::endl; }
- 编译指令
namespace A { int a = 1; } //编译指令 void instruct() { int a = 2; using namespace A; //不报错 std::cout << a << std::endl; //2,优先就近原则的变量 }
对C
的扩展与增强
一、全局变量检测
c
语言int a; int a = 1; //可正常运行
c++
对这种重复定义的全局变量,会在编译时检测到并报错
二、函数检测
包括:形参类型检测、实参个数检测、返回值检测
c
语言getResult(w, h) { //未定义类型与返回值(甚至定义了返回值,但是没有return代码也不会报错) return w * h; } void test() { printf("%d\n", getResult(10, 10, 10)); //100,参数个数不一致也可以正常执行并返回 }
c++
int getResult(int w, int h) { return w * h; } void test() { int result = getResult(10, 10); }
三、类型转换检测
c
语言char * p = malloc(64); //malloc返回的是void*,但是可以用其他,如char*去接收
c++
char * P = (char *)malloc(64); //必须进行强转
四、结构体
c
语言//1. 结构体中不允许有函数 //2. 创建结构体变量必须要有struct关键字 struct Person { int age; //void func(); //报错,不允许有函数 } void test(){ struct Person P; //必须有struct关键字 p.age = 100; }
c++
struct Person { int age; void func(); //可以有函数声明 void func2() { //也可以有函数实现 age++; }; } void test(){ Person P; //可以省略掉struct关键字 p.age = 100; p.func2(); cout << p.age << endl; //101 }
五、布尔类型
c++
新增bool
布尔类型
bool flag; //默认 false,false-0;true-1
flag = 100;
cout << flag; //1,将非0值都转为1
cout << sizeof(bool); //1,只有一个字节
六、三目运算
c
语言void test(){ int a = 10; int b = 20; printf("ret = %d\n", a > b ? a : b); //ret = 20 //a > b ? a : b = 100; //报错,c语言中返回的是值,即 20 = 100 这种语法是错误的 *(a > b ? &a : &b) = 100 printf("%d\n", b); //100 }
c++
void test(){ int a = 10; int b = 20; printf("ret = %d\n", a > b ? a : b); //ret = 20 a > b ? a : b = 100; //通过,c++中返回的是变量,即 b = 100 这种语法是可以的 cout << b << endl; //100 }
七、const
c
语言//a.c const int ttt = 1000; //c语言中const修饰的全局变量默认是外部链接属性
//b.c const int a = 100; //全局const void test1(){ //a = 200; //编译就报错,常量无法直接修改 int *p = &a; *p = 200; //运行时报错 } void test2(){ const int a = 100; //局部const int *p = &a; *p = 200; //修改成功,所以说const是个伪常量 printf("%d\n", a); //200 int arr[a]; //不允许,报错,因为a是个伪常量 } void test3(){ extern const int ttt; //无需引入头文件之类的 printf("%d\n", ttt); //100,可以访问成功 }
c++
//a.cpp const int ttt = 1000; //c++中const修饰的全局变量默认是内部链接属性 extern const int tt = 10000; //可以通过extern关键字提升为外部链接属性
//b.cpp const int a = 100; //全局const,同c语言 void test1(){ //a = 200; //编译就报错,常量无法直接修改 int *p = (int *)&a; //直接&a返回的是const int * *p = 200; //运行时报错 } void test2(){ const int a = 100; //局部const int *p = (int *)&a; //存在隐式操作:int temp = a; int *p = (int *)&a; *p = 200; cout << a << endl; //100,运行不报错,但是修改的是隐式操作中的变量值 int arr[a]; //可以,c++下const是常量,允许初始化数组 } void test3(){ extern const int ttt; cout << tt <<< endl; // 10000 cout << ttt <<< endl; //运行时报错(链接阶段报错):1个无法解析的外部命令 }
- 内存分配
- 对
const
变量取地址,会分配临时内存,不可以修改void test(){ const int a = 10; int *p = (int *)&a; //int temp = a; int *p = (int*)&temp; *p = 10000; cout << a << endl; //10 }
- 通过普通变量来初始化
const
变量,会分配内存,可修改void test(){ int b = 10; const int a = b; int *p = (int *)&a; *p = 10000; cout << a << endl; //10000 }
- 对于自定义数据类型,会分配内存,可修改
#include <string> struct Person { string name; int age; } void test(){ const Person p; //p.age = 10; //不可以直接修改 Person * pp = (Person *)&p; (*pp).name = "Tom"; pp -> age = 10; cout << p.name << endl; //Tom cout << p.age << endl; //10 }
- 对
- 内存分配
- 在
c++
中尽量用const
而非宏定义
const
有类型,但是#define
没有#define MAX 1024; //一旦出现报错,报错中看到的是1024,不利于问题定位
const
有作用域,但是```#define``不重视作用域#define
默认定义到文件结尾,如果定义在指定作用下有效的常量,那么#define
就不能用了
引用
引用的本质
引用的本质是在
c++
内部实现的一个指针常量
Type &ref = val; // Type *const ref = &val;
变量的引用
void test(){
int c = 10;
int a = 10;
int &b = a; //&符号:表示b是对a的一个引用(一个别名的意思)
//int &c; //报错,引用必须申明时初始化
// &b = c; //运行报错,引用一旦初始化后就不可以引用其他变量
b = 100;
cout << a << endl; //100
cout << b << endl; //100
}
int main(){
test();
return 0;
}
数组的引用
- 基本用法
- 直接建立引用
void test(){ int arr[10]; int (&pArr)[10] = arr; for(int i = 0; i < 10; i++) { arr[i] = 100 + i; } for(int i = 0; i < 10; i++) { cout << pArr[i] << endl; } }
- 先定义数组类型,再通过类型定义引用
void test(){ int arr[10]; for(int i = 0; i < 10; i++) { arr[i] = 100 + i; } typedef int(ARRAY_TYPE)[10]; //定义好类型 ARRAY_TYPE &pArr = arr; for(int i = 0; i < 10; i++) { cout << pArr[i] << endl; } }
- 参数的传递方式:引用
参数的传递方式有:值传递、地址传递、引用传递
void test(int &a, &b){ int temp = a; a = b; b = temp; } int main(){ int a = 10; int b = 20; test(a, b); cout << a << endl; //20 cout << b << endl; //10 }
指针的引用
c
语言/* * p: 二级指针,指向指针的指针 * *p: 指针,指向Person * **p: Person */ struct Person { int age; } void allocateSpace(Person **p){ *p = (struct Person *)malloc(sizeof(struct Person)); (*p) -> age = 10; } void test(){ struct Person *p = Null; allocateSpace(&p); printf("%d\n", p -> age) //10 }
c++
//无需高级指针(一级用二级),用同级指针操作即可 struct Person { int age; } void allocateSpace(Person* &pp){ // Person * &pp = p; pp = (Person *)malloc(sizeof(Person)); pp -> age = 20; } void test(){ struct Person *p = Null; allocateSpace(&p); cout << p->age << endl; //20 }
常量的引用
void test(){
const int &ref = 10; //加入const后,进行了一个隐式操作(分配到一个临时空间):int temp = 10; const int &ref = temp;
int *p = (int *)&ref;
*p = 10000;
cout << ref << endl; //10000,这个10是temp临时空间的,即通过普通变量来初始化const变量,所以可以进行修改
}
//使用场景:修饰函数形参,防止误修改操作
void test2(const int &a){
a = 10000; //报错,无法修改常量
}
引用的注意事项
- 引用的必须是一块合法的内存空间
int &a = 10; //语法错误,10不是一个合法的内存空间 const int &a = 10; //可以,详见常量的引用
- 不要返回局部变量的引用
int& func(){ int a = 10; return a; } int& func2(){ static int a = 10; return a; } int main(){ int &ref = func(); cout << ref << endl; //func结束局部变量释放,可能是随机的一个乱码值 int &ref2 = func2(); cout << ref2 << endl; //10,通过static修饰符,变量不会被函数释放 func2() = 1000; //此时还可作为左值进行修改 cout << ref2 << endl; //1000 return 0; }
函数
内联函数
- 宏函数的缺陷
- 必须通过加括号来确保运算的完整
#include <iostream> #define MY_ADD(x, y) x + y #define MY_ADD2(x, y) ((x) + (y)) void test_defect1(){ int a = 10; int b = 20; int ret = MY_ADD(a, b) * 20; int ret2 = MY_ADD2(a, b) * 20; std::cout << ret << std::endl; //410: 10 + 20 * 20 std::cout << ret2 << std::endl; //600: (10 + 20) * 20 }
- 即使加了括号,某些运算依旧不符合预期
#define MY_COMPARE(x, y) (((x) < (y)) ? (x) : (y)) void test_defect2() { int a =10; int b = 20; int ret = MY_COMPARE(++a, b); std::cout << ret << std::endl; //预期11,实际12: (((++x) < (y)) ? (++x) : (y)) }
- 内联函数
本身也是一个真正的(普通)函数,具有普通函数的所有行为,但是它又会在适当的地方像预定义宏一样展开,从而免去函数调用的开销
/** * 1. 在普通函数前面加上 inline 关键字即可变为内联函数 * 2. 内联函数的声明和定义必须在一起,否则编译器将视为普通函数存在(或者声明和实现同时加关键字) * 3. 内联函数的确会占用空间,但是省去了普通函数调用的压栈、跳转、返回等的开销,相当于是用空间换时间 */ inline void test_inline1(); inline void test_inline1(){} inline int test_inline2(int a, int b) { return a + b; }
- 类函数
在类内部定义的函数(包括构造函数),都隐式加了inline关键字,即都会自动成为内联函数
- 内联函数与编译器
内联函数只是给编译器一个建议,编译器不一定会接受,而且即使没有将函数声明为内联函数,编译器也可能会将函数进行内联编译
以下情况编译器可能考虑不进行内联编译:- 存在任何形式的循环语句
- 存在过多的条件判断语句
- 函数体过于庞大
- 对函数进行了取地址操作(因为直接跑的源码,没有函数入口)
函数的参数默认值与占位参数
- 参数默认值
int func1(int a = 10) {} //以下会报错,声明和实现只能有一个提供参数默认值,不然会不知道应该用声明的参数a的值,还是定义中参数a的值 int func2(int a = 10); int func2(int a = 10){}
- 占位参数
void func1(int a, int) {}
void func2(int = 1) {} //占用参数也可以设置默认值
void test(){
func1(10, 1); //占位参数必须传入
}
函数重载
指在同一个作用域下,允许参数(个数、类型、顺序)不同、函数名相同的多个函数存在
//int func(){} //类型不可以
void func(){}
void func(int a){}
void func(double a){}
void func(double a, int b){}
void func(int a, double b){}
注意点
void func(int &a) {
cout << a << endl;
}
void func(const int &a) {
cout << a << endl;
}
//注意一:避免二义性出现
//void func(int a){} //虽然可以和上面两个同时存在,但是调用时存在多个可调用,会报错,所以和第一个最好只有一个在
void test1(){
int a = 10;
func(a); //调用的是第一个
func(10); //调用的是第二个
}
//注意二:默认参数也要避免二义性
void func1(int a, int b = 10){}
void func1(int a)
void test2(){
func1(10); //报错,两个函数都可以调用,出现了二义性
}
实现原理
为了实现函数重载,编译器会做一些幕后工作,对于不同参数的同名函数,比如
void func()
,编译器可能会将其函数名修饰成_func
,当遇到void func(int a)
,则将函数名修饰为_func_int
,以此类推,当然这里只是说可能,因为不同的编译器可能有不同的修饰函数名的标准
extern "C"
由于
c++
中存在函数重载,编译器会将函数名进行修饰,而如果调用的函数来自c
,将会出现找不到函数的错误,此时就需要使用externC
来申明函数来自外部,需要从外部找,且链接方式为c
语言的链接方式
test.h
#include <stdio.h> void show();
test.c
#include "test.h" void show(){ printf("hello c"); }
main.cpp
//#include "test.h" //使用extern C后会进行引入,不用再引入 extern "C" void show(); //告诉编译器,show函数用C语言方式进行链接 int main(){ show(); //如果不使用extern C,由于C++的函数重载,链接时查找的函数名可能就是_show_void,显然c文件中并不存在该函数,导致连接失败而报错 return 0; }
- 在
.h
文件中进行extern C
在
.cpp
中进行extern C
需要罗列所有所需的函数,十分的不便,因此,一般都是在.h
中进行//test.h //表示如果是c++语言的话,就将代码放入 extern "C" { //... } 中 #ifdef __cplusplus extern "C" { #endif # include <stdio.h> void show(); #ifdef __cplusplus } #endif
类和对象
struct
和class
的区别
class
默认是private
,而struct
则是public
权限
public
:类内类外皆可访问protected
:只有类内可以访问(子类可访问)private
:只有类内可以访问(子类不可以访问)
class Person {
public:
string name; //public
int age; //public
protected:
string car; //protected
private:
int pwd; //private
public:
void func() { //public
name = "张三"
}
}
构造函数
对象的初始化,同
java
class Person {
public:
Person() { //... } //无参构造
Person(paramType param, ...) {} //有参构造
}
分类
- 按照参数分类
- 无参构造(默认构造函数)
- 有参构造
- 按照类型分类
- 普通构造
- 拷贝构造
/* * const:防止拷贝的对象被修改 * &:采用引用传递的方式而不是值传递的方式,防止无限套娃,因为值传递实质就是调用的拷贝构造函数 */ class Person(const Person &p) { attr1 = p.attr1; //... }
调用
class Person {
public:
string name;
int age;
Person() {} //无参构造
Person(string n, int a) { //有参构造
name = n;
age = a;
}
Person(const Person &p){ //拷贝构造
name = p.name;
age = p.age;
}
explicit Person(int a) { //使用explicit关键字,可以禁止隐式调用法
age = a;
}
}
括号法调用
void test(){
Person p("张三", 10); //有参构造
Person p2(p); //拷贝构造
}
显示法调用
void test(){
Person p = Person("张三", 10); //有参构造
Person p2 = Person(p); //拷贝构造
}
隐式类型转换法
void test(){
//不建议使用
Person p = ("张三", 10); //有参构造
Person p2 = p; //拷贝构造 === 由此可见,值传递本质就是调用拷贝构造
}
无参构造的调用
不要使用括号法,会被当成函数声明
void test(){
Person p;
//Person p() //会被当做是test函数中进行的一个:返回值为Person类型,函数名为p 的函数声明
}
匿名对象
特点:当前行执行完后立即释放
注意:不要用拷贝构造来初始化匿名对象,否则会不知道
class Person{
public:
int age;
Person(int a){ age = a; }
Person(const Person &p) { age = p.age }
}
void test(){
Person(10); //匿名对象,当前行执行完后立即释放
Person p = Person(10);
Person(p); //会报错,会被编译器认为在创建一个p对象,但是p对象已经存在,所以抛出Person p重定义错误
}
拷贝构造的调用时机
- 用已创建好的对象来初始化新的对象
void test(){ Person p(10); Person p2 = Person(p); //会触发拷贝构造 }
- 用值传递的方式进行参数传递
void func(Person p) {} //相当于Person p = p1,所以这里会触发拷贝构造 void test(){ Person p1(10); func(p1); }
- 以值的方式返回局部对象
Person func() { Person p; return p; //p属于栈中对象,方法结束随之销毁,这里会隐式地用p拷贝出一个匿名对象并返回,会触发拷贝构造 } void test(){ Person p = func(); }
注意:编译器是可能会进行优化的,如将
debug
版本改为release
版本,上面的代码可能就会被优化为void func(Person &p){}; //将函数func()优化成这样 Person p; func(p); //调用时创建一个p对象,然后作为参数传入,这样一来就不会调用到拷贝构造函数了
初始化列表
class Family{
public:
int count;
Family(int c): count(c) {}
}
class Person{
public:
string name;
Family family;
Person(): name("张三"), family(20) { //会调用Family的有参构造
}
Person(string n, int count): name(n), family(count) {
}
}
void test(){
Person p;
cout << p.name << endl; //"张三"
}
析构函数
对象的清理
class Person {
/*
* 1.无返回值
* 2.函数名同类名,加上前缀符号~
* 3.不可以有参数、不可以重载
* 4.对象释放时编译器自动调用一次,如将对象放在方法中,方法结束时就会调用
*/
~Person() {
//...
}
}
默认函数
默认情况下,编译器会自动添加这四个函数:默认构造函数(空实现)、拷贝构造函数(值拷贝:浅拷贝)、析构函数(空实现)、
operator=
如果用户自行添加了(有参|拷贝)构造函数,编译器将不会自行添加默认构造函数
深浅拷贝
浅拷贝的问题与析构函数的用途
通常可以用析构函数来释放堆内存
以下代码会报错:通过有参构造,给
p
的name
存放在堆中,p2
通过默认的拷贝构造函数浅拷贝了p
,即二者共用着堆中的这个name
,当函数test_copy
调用结束,p2
(栈先进后出,所以先p2
)触发析构函数,将name
释放,但是紧接着p
又来释放一次,导致重复释放的错误
#include <iostream>
#include <string.h>
using namespace std;
class Person{
public:
char * name;
int age;
Person(const char* n, int a) {
name = (char *)malloc(strlen(n) + 1);
strcpy(name, n);
age = a;
}
~Person(){
if (name != nullptr) {
free(name);
name = nullptr;
}
}
};
void test_copy() {
Person p("张三", 20);
cout << p.name << endl;
Person p2(p);
cout << p2.name << endl;
}
int main(){
test_copy();
return EXIT_SUCCESS;
}
采用深拷贝的解决方案
通过自行定义拷贝构造,对对象进行深拷贝
#include <iostream>
#include <string.h>
using namespace std;
class Person{
public:
char * name;
int age;
Person(const char* n, int a) {
name = (char *)malloc(strlen(n) + 1);
strcpy(name, n);
age = a;
}
Person(const Person &p) {
name = (char *)malloc(strlen(p.name) + 1);
strcpy(name, p.name);
age = p.age;
}
~Person(){
if (name != nullptr) {
free(name);
name = nullptr;
}
}
};
void test_copy() {
Person p("张三", 20);
cout << p.name << endl;
Person p2(p);
cout << p2.name << endl;
}
int main(){
test_copy();
return EXIT_SUCCESS;
}
动态创建对象
c
语言的动态分配内存
//分配
char* name = (char*)malloc(strlen("张三") + 1);
if (NULL == name) return 0; //可能分配失败
//释放
if (NULL != name) {
free(name);
name = NULL;
}
- 缺点
- 必须确定对象的长度
- 必须对返回的
void *
类型进行强转 - 可能申请内存失败,必须对分配成功与否进行判断
c++
的new
与delete
-
作用
new
:用于分配(堆中)内存
delete
:用于释放(堆中)内存class Person { public: Person(){} ~Person(){} } void test(){ Person * p = new Person(); //在堆中创建了一个Person对象p delete p; //释放堆中的Person对象p }
-
和
malloc
和free
的区别malloc
和free
属于库函数,new
与delete
属于运算符malloc
不会调用构造函数,new
可以malloc
返回的是void*
,new
直接返回对应的对象类型
-
注意事项
不要用万能指针
void*
去接收new
的对象,会导致delete
不知道要释放多少内存而失效 -
数组的创建与销毁
//堆上:一般类型 int* arr = new int[10]; //堆上:复杂类型 Person* p = new Person[10]; //会调用(10次)默认构造,所以最好给创建的class都手动添加上默认构造 Person* p = new Person[10]{Person(10), Person(20)}; //不建议,不是所有编译器都支持这种语法 delete [] p; //默认只会释放一个导致释放不干净而报错(有些编译器没有报错提示但实质还是有问题的),所以要加上[] //栈上开辟数组:此时可以没有默认构造(不会自动去调用默认构造并创建对象) Person[10] p2 = {Person(10), Person(20)};
static
在类定义中,它的成员(包括成员变量和成员函数)如果加上
static
关键字,表示声明为静态的,称为静态成员
无论这个类创建了多少对象,静态成员都只存在一个拷贝,且这个拷贝被所有属于这个类的对象共享
静态成员变量
#include<iostream>
using namespace std;
class Person {
public:
//1.声明:编译阶段就分配了内存(输出在main函数之前)
static int age;
};
int Person::age = 0; //2.初始化:类内声明、类外初始化
int main(){
//3.访问:通过对象访问
Person p1;
cout << p1.age << endl;
//3.访问:通过类名访问(推荐)
cout << Person::age << endl;
return EXIT_SUCCESS;
}
静态成员函数
#include<iostream>
using namespace std;
class Person {
public:
int age;
static void func(){ //所有对象共享此函数
//age = 10; //报错,静态函数中只能访问静态变量,无法直接访问普通变量
cout << "static func" << endl;
}
};
int main(){
//访问:通过对象访问
Person p1;
p1.func();
//访问:通过类名访问(推荐)
Person::func();
return EXIT_SUCCESS;
}
单例
#include<iostream>
class Foo {
private:
Foo(){};
Foo(const Foo & foo){} //防止通过拷贝构造创建出新的对象
static Foo *instance; //设为私有的--防止被修改
public:
static Foo* getInstance() {
return instance;
}
};
Foo * Foo::instance = new Foo; //通过指定Foo作用域,即可视为类内部访问,故可以调用构造器
int main() {
Foo* bar1 = Foo::getInstance();
Foo* bar2 = Foo::getInstance();
std::cout << (bar1 == bar2) << std::endl; //1
return EXIT_SUCCESS;
}
对象的大小
空对象大小
#include <iostream>
using namespace std;
class Person{};
void test() {
Person p1;
/**
* sizeof = 1
* 结果:空对象的字节大小为1
* 原因:所有的对象都有一个独一无二的内存地址,所以也会给空对象分配一个1字节的内存地址
*/
cout << "sizeof = " << sizeof(p1) << endl;
}
int main() {
test();
return EXIT_SUCCESS;
}
非空对象大小
#include <iostream>
using namespace std;
class Person{
int age;
static string name; //静态成员变量属于类,不属于对象
static void func1() {
//静态成员函数也属于类,不属于对象
}
void func2() {
//成员函数并不属于类对象,和对象是分开存储的
}
};
void test() {
Person p1;
/**
* sizeof = 4
* 结果:字节大小为4(注意和结构体一样存在对齐填充)
* 原因:此时对象中已有数据,无需特意去标识对象位置,直接使用数据地址即可
*/
cout << "sizeof = " << sizeof(p1) << endl;
}
int main() {
test();
return EXIT_SUCCESS;
}
this
指针
概述
c++
中(非静态)成员函数,只会产生一份函数实例,所有的对象公用这份实例代码
c++
通过特殊的指针this
来指向被调用的成员函数所属的对象,以此得知此时成员函数中的成员变量是属于哪个对象的
this
指针隐式地存在每个(非静态)成员函数中(第一个隐藏参数)
静态成员函数内部没有
this
指针,也因此在其内部中无法直接操作非静态成员
#include <iostream>
using namespace std;
class Person{
public:
int age;
Person(int age){
this->age = age;
}
};
int main() {
Person p1(18);
cout << p1.age << endl; //18
return EXIT_SUCCESS;
}
链式编程
Person& func(Person & p) {
//...
return *this;
}
//注意如果返回的是值,得到的将是拷贝构造创建出来的新对象
Person func(Person & p) {
//...
return *this;
}
空指针访问成员函数
#include <iostream>
using namespace std;
class Person{
private:
int age = 10;
public:
void showClassName(){
cout << "class name is Person" << endl;
}
void showAge(){
cout << "age is " << this->age << endl;
}
};
int main(){
Person* p = NULL;
//p->showAge(); //报错,this为NULL,没有age属性
p->showClassName(); //没有使用到this,可以正常访问
return EXIT_SUCCESS;
}
常函数与常对象
常函数
class Person{
private:
int age = 10;
public:
void showAge() const //3. 这个const意味着:const Person * const this,表示常函数,此时无法修改this指向的值了
{
/**
* 0.this的本质:Person * const this
* 1. 指向无法修改
* 2. 指向的值可以修改
*/
//1. this = NULL; //报错,this指向无法修改
//2. 这里只是展示年龄,如何禁止修改年龄?也就是如何禁止修改this指向的值呢?
cout << this->age << endl;
}
};
常对象
class Person{
public:
int age = 10;
void func1(){}
void func2() const{}
};
int main(){
const Person p;
//p.age = 30; //报错,加上const后表示常函数,无法对值进行修改
//p.func1(); //报错,无法访问非常函数,因为在普通函数func1中是可以修改age的,这与常对象相悖
p.func2(); //可正常访问
return EXIT_SUCCESS;
}
mutable
关键字
class Person{
public:
mutable int age = 10;
void showAge() const {
age = 20; //使用mutable关键字修饰后,可以进行修改
cout << this->age << endl;
}
};
int main(){
const Person p;
p.age = 30; //使用mutable关键字修饰后,可以进行修改
return EXIT_SUCCESS;
}
友元
概述
类的主要特点之一就是数据隐藏,即类的私有成员无法在类的外部访问,但是有时候又需要在外部进行访问,这时候就需要通过友元函数来实现了
友元函数是一种特权函数,
c++
允许这个特权函数访问私有成员
可以将一个全局函数、类中的某个成员函数、甚至整个类声明为友元
友元 - 全局函数
#include <iostream>
#include <string>
using namespace std;
class Room {
friend void myFriend(Room &room); //将需要访问私有成员的全局函数的声明放入类中,并用friend关键字修饰
public:
Room() {
this->settingRoom = "客厅";
this->bedRoom = "卧室";
}
string settingRoom;
private:
string bedRoom;
};
void myFriend(Room &room) {
cout << room.settingRoom << endl;
cout << room.bedRoom << endl;
}
void test_friend() {
Room *room = new Room();
myFriend(*room);
}
int main() {
test_friend();
return EXIT_SUCCESS;
}
友元 - 类
#include <iostream>
#include <string>
using namespace std;
/*先声明,防止紧接着的MyFriend报错*/
class Room;
/*朋友类*/
class MyFriend {
public:
MyFriend(); //类内定义,类外去实现
void visit();
Room *room;
};
/*房间类*/
class Room {
friend class MyFriend;
public:
Room();
string settingRoom;
private:
string bedRoom;
};
/*房间类类外实现*/
Room::Room() {
this->settingRoom = "客厅";
this->bedRoom = "卧室";
}
MyFriend::MyFriend() {
this->room = new Room();
}
void MyFriend::visit() {
cout << room->settingRoom << endl;
cout << room->bedRoom << endl;
}
void test_friend2() {
MyFriend myFriend;
myFriend.visit();
}
int main() {
test_friend2();
return EXIT_SUCCESS;
}
友元 - 成员函数
class Room {
friend void MyFriend::visit();
//...
}
友元 - 全局函数的类内实现
class Room {
public:
string m_Desc;
Room(string desc) {
this->m_Desc = desc;
}
friend void descript() {
cout << m_Desc << endl;
}
}
void test() {
Room room("this is a room.");
descript();
}
继承(派生)
示例代码
#include <iostream>
#include <string>
using namespace std;
class Person {
protected:
string m_Name;
int m_Age;
public:
Person(string name, int age) : m_Name(std::move(name)), m_Age(age) {}
void info() {
cout << "name is " << this->m_Name << ", age is " << this->m_Age << endl;
}
};
/**
* : 表示继承
* public 表示继承方式
*/
class Teacher : public Person {
private:
string profession = "teacher";
public:
Teacher(const string &name, int age) : Person(name, age) { //调用父类的构造
//...
}
void job() {
cout << "my job is a " << this->profession << endl;
}
};
void test() {
Teacher teacher("张三", 12);
teacher.info();
teacher.job();
}
int main() {
test();
return EXIT_SUCCESS;
}
继承方式
继承方式共有三种:
public
、protected
、private
,可以理解为继承之后原父类成员集成到子类中后所属的访问权限
父类的私有属性也会被继承,只不过被编译器隐藏了,无法访问到
所有构造函数、拷贝构造、析构函数、
operator=
,这些都不会被继承
- 父类
class Father { public: int a; protected: int b; private: int c; }
public
class Child: public Father { public: int a; protected: int b; }
protected
class Child: protected Father { protected: int a; protected: int b; }
private
class Child: private Father { private: int a; private: int b; }
- 私有属性实质也继承了
class Child: private Father { private: int d; } //... cout << sizeof(Child) << endl; //16 (a + b + c + d) //...
执行顺序
构造函数执行顺序:父类(无参)构造、成员属性类构造、自身构造
析构函数执行顺序:根据构造的顺序先入后出
#include <iostream>
#include <string>
using namespace std;
class Father {
public:
Father() {
cout << "父类构造函数" << endl;
}
~Father() {
cout << "父类析构函数" << endl;
}
};
class Other {
public:
Other() {
cout << "成员类构造函数" << endl;
}
~Other() {
cout << "成员类析构函数" << endl;
}
};
class Son: public Father {
public:
Son() {
cout << "子类构造函数" << endl;
}
~Son() {
cout << "子类析构函数" << endl;
}
private:
Other other;
};
void test() {
Son son;
/**
* 父类构造函数
* 成员类构造函数
* 子类构造函数
* 子类析构函数
* 成员类析构函数
* 父类析构函数
*/
}
int main() {
test();
return EXIT_SUCCESS;
}
同名处理
普通成员
#include <iostream>
using namespace std;
/**
* 1.子类父类同名函数,优先用子类函数
* 2.子类含有父类同名函数后,会隐藏掉所有父类的该名函数,包括重载函数
* 3.想要继续访问父类同名函数,需要用作用域的方式显示调用
*/
class Father {
protected:
int m_Age = 31;
public:
void info() {
cout << "father age is " << this->m_Age << endl;
}
void info(int age) {
cout << "father age is " << age << endl;
}
};
class Son : public Father {
private:
int m_Age = 1;
public:
void info() {
cout << "son age is " << this->m_Age << endl;
}
};
void test() {
Son son;
son.info();
//son.info(32); //报错,无法调用
son.Father::info();
son.Father::info(32);
}
int main() {
test();
return EXIT_SUCCESS;
}
静态成员
#include <iostream>
using namespace std;
/**
* 1.所有结论通普通成员
* 2.对于静态成员,除了使用对象方式访问,还可以直接用类进行访问
*/
class Father {
public:
static int m_Age;
static void info() {
cout << "father age is " << m_Age << endl;
}
static void info(int age) {
cout << "father age is " << age << endl;
}
};
int Father::m_Age = 31;
class Son : public Father {
public:
static int m_Age;
static void info() {
cout << "son age is " << m_Age << endl;
}
};
int Son::m_Age = 1;
void test() {
cout << Son::m_Age << endl; //1
Son::info(); //son age is 1
Father::info(); //father age is 31
Son::Father::info(); //father age is 31
Son::Father::info(32); //father age is 32
}
int main() {
test();
return EXIT_SUCCESS;
}
多继承
基本语法
class 骡: public 驴, public 马 { //... }
菱形继承与虚继承
- 菱形继承
如:驴和马都继承了动物类,骡子又同时继承了驴和马两个类。
这样的继承关系就称为菱形继承。 - 菱形继承的问题
- 二义性
驴和马都有
age
属性,导致骡调用时不得不指定作用域,否则存在二义性导致报错 - 数据浪费
骡子从驴和马中分别继承了一份
age
数据,这显然是浪费且无必要的
- 虚继承
解决菱形继承中存在的问题
#include <iostream> using namespace std; /* * 使用virtual关键字后 * 1.Animal称为虚基类 * 2.Horse和Donkey不在直接继承Animal的age属性,而是存着一个名为的vbptr(virtual base pointer)虚基类指针 * 3.vbptr指针指向vbtable通过vbtable可以找到对应类(如Horse)的(到age的)偏移量,通过这个偏移量可以定位到Animal的age * 4.可知现在Horse、Donkey、Mule都用的一个age(Animal中的age) * 5.Mule可以直接调用age属性 */ /*动物类*/ class Animal{ public: int age; }; /*马类*/ class Horse: virtual public Animal {}; /*驴类*/ class Donkey: virtual public Animal {}; /*骡类*/ class Mule: public Horse, public Donkey {}; void test() { Mule mule; mule.age = 20; cout << mule.Horse::age << endl; //20 cout << mule.Donkey::age << endl; //20 cout << mule.age << endl; //20 } int main() { test(); return EXIT_SUCCESS; }
重载 & 重写 & 重定义
重载
- 同一个作用域
- 参数个数、顺序、或类型不同
- 与函数返回值无关
const
也可以作为重载条件(do(const int a){}; do(int a){}
)
重写
- 存在继承
- 子类重新定义父类的
virtual
函数 - 函数返回值、名称、参数必须和父类的虚函数一致
- 如果父类是个常函数,子类不能丢掉
const
关键字
重定义
- 存在继承
- 子类重新定义父类的同名非
virtual
函数
多态
静态多态与动态多态
c++
支持编译时多台(静态多态)和运行时多态(动态多态)。
像运算符重载和函数重载就是编译时多态,
而派生类和虚函数的实现则是运行时多态。
静态多态和动态多态的区别就是:函数地址是早绑定(静态联编)还是晚绑定(动态联编)。
如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是说地址时早绑定的,称之为静态多态;
如果函数的调用地址需要在运行时才能确定,这就是晚绑定,也就是动态多态。
一般而言,所谓多态,指的是动态多态。
虚函数
- 静态联编
#include <iostream> using namespace std; class Animal{ public: void say() { cout << "Animal is saying ..." << endl; } }; class Cat : public Animal { public: void say() { cout << "Cat is saying ..." << endl; } }; void whoIsSpeaking(Animal& animal) { /* * 结果:Animal is saying ... * 结论:默认为静态联编,即编译器就已经将此函数(say)的地址确定为Animal的say的地址 */ animal.say(); } void test() { Cat cat; whoIsSpeaking(cat); } int main() { test(); return EXIT_SUCCESS; }
- 动态联编
#include <iostream> using namespace std; class Animal{ public: virtual void say() { //使用virtual关键字修饰,称为虚函数 cout << "Animal is saying ..." << endl; } }; class Cat : public Animal { public: void say() { //子类对于virtual关键字,可加可不加 cout << "Cat is saying ..." << endl; } }; void whoIsSpeaking(Animal& animal) { /* * 结果:Cat is saying ... * 结论:虚函数属于动态联编,在运行时才确定函数的地址 */ animal.say(); } void test() { Cat cat; whoIsSpeaking(cat); } int main() { test(); return EXIT_SUCCESS; }
- 原理
class Animal1 { public: void speak() {} }; class Animal2 { public: virtual void speak() {} }; void test() { cout << "Animal1: " << sizeof(Animal1) << endl; //空对象:1 cout << "Animal2: " << sizeof(Animal2) << endl; //8 }
当使用了
virtual
关键字创建虚函数后,该类就会产生一个vfptr
,即虚函数(表)指针
vfptr
指向vftable
虚函数表
vftable
中存储着该类该函数的函数地址
当子类继承了父类时,就会继承父类的vfptr
和vftable
如果子类重写了父类的该函数,则子类中的vftable
中的原本父类的函数的地址就会被子类自身的该函数地址覆盖
Animal *animal = new Cat();
,只是用父类类型指针去接收,本质还是子类对象,所以运行时就会找到子类的vftable
中的函数地址并进行调用
纯虚函数与抽象类
class Abstract {
virtual 返回值 函数名(形参列表) = 0; //纯虚函数
}
如果一个类包含了纯虚函数,那么该类无法被实例化,并称该类为抽象类
抽象类的子类必须重写父类的纯虚函数,否则也是个抽象类
虚析构与纯虚析构
- 多态下默认不会调用子类的析构函数
#include <iostream> #include <cstring> using namespace std; class Animal{ public: char* m_Name; Animal() { cout << "Animal 无参构造" << endl; } ~Animal() { cout << "Animal 析构函数" << endl; } }; class Cat: public Animal{ public: Cat() { cout << "Cat 无参构造" << endl; } Cat(const char* name) { cout << "Cat 有参构造" << endl; this->m_Name = new char[strlen(name)]; strcpy(this->m_Name, name); } ~Cat() { cout << "Cat 析构函数" << endl; if (this->m_Name != nullptr) { delete [] this->m_Name; this->m_Name = nullptr; } } }; void test() { /* * Animal 无参构造 * Cat 有参构造 * Animal 析构函数 */ Animal *animal = new Cat("汤姆"); delete animal; //堆中创建的需要手动调用delete去释放 } int main() { test(); return EXIT_SUCCESS; }
- 使用虚析构
#include <iostream> #include <cstring> using namespace std; class Animal{ public: char* m_Name; Animal() { cout << "Animal 无参构造" << endl; } virtual ~Animal() { //虚析构 cout << "Animal 析构函数" << endl; } }; class Cat: public Animal{ public: Cat() { cout << "Cat 无参构造" << endl; } Cat(const char* name) { cout << "Cat 有参构造" << endl; this->m_Name = new char[strlen(name)]; strcpy(this->m_Name, name); } ~Cat() { cout << "Cat 析构函数" << endl; if (this->m_Name != nullptr) { delete [] this->m_Name; this->m_Name = nullptr; } } }; void test() { /* * Animal 无参构造 * Cat 有参构造 * Cat 析构函数 * Animal 析构函数 */ Animal *animal = new Cat("汤姆"); delete animal; //堆中创建的需要手动调用delete去释放 } int main() { test(); return EXIT_SUCCESS; }
- 使用纯虚析构
纯虚析构需要有声明,也要有实现,且类内声明,类外实现
如果一个类含所有纯虚析构,它也属于抽象类,无法实例化#include <iostream> #include <cstring> using namespace std; class Animal{ public: char* m_Name; Animal() { cout << "Animal 无参构造" << endl; } virtual ~Animal() = 0; //纯虚析构 }; Animal::~Animal() { cout << "Animal 析构函数" << endl; } class Cat: public Animal{ public: Cat() { cout << "Cat 无参构造" << endl; } Cat(const char* name) { cout << "Cat 有参构造" << endl; this->m_Name = new char[strlen(name)]; strcpy(this->m_Name, name); } ~Cat() { cout << "Cat 析构函数" << endl; if (this->m_Name != nullptr) { delete [] this->m_Name; this->m_Name = nullptr; } } }; void test() { /* * Animal 无参构造 * Cat 有参构造 * Cat 析构函数 * Animal 析构函数 */ Animal *animal = new Cat("汤姆"); delete animal; //堆中创建的需要手动调用delete去释放 } int main() { test(); return EXIT_SUCCESS; }
类型转换
父转子
存在问题,一旦访问只属于子类的成员就会报错
Animal *animal = new Animal;
Cat *cat = (Cat)animal;
子转父
不存在问题,因为父类的私有成员实质上也继承到了子类上,所以子转父后即使访问父类的私有成员也不会报错
Cat *cat = new Cat;
Animal *animal = (Animal)cat;
案例
自定义数组
MyArray.h
#ifndef CPLUSPLUS_MYARRAY_H #define CPLUSPLUS_MYARRAY_H #include <iostream> using namespace std; class MyArray{ public: MyArray(); //默认构造 MyArray(int capacity); //有参构造 MyArray(const MyArray & arr); //拷贝构造 ~MyArray(); //析构函数 void insert(int val); //尾插 void setData(int pos, int val); //指定位置插入 int getData(int pos); //获取指定位置数据 int getCapacity(); //获取容量 int getSize(); //获取大小 int &operator[](int index); private: int m_Capacity; //数组容量 int m_Size; //数组大小 int * pAddress;//数组指针 }; #endif //CPLUSPLUS_MYARRAY_H
MyArray.cpp
#include "MyArray.h" MyArray::MyArray() { cout << "默认构造" << endl; new(this) MyArray(100); } MyArray::MyArray(int capacity) { cout << "有参构造" << endl; this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new int[this->m_Capacity]; } MyArray::MyArray(const MyArray &arr) { cout << "拷贝构造" << endl; this->m_Capacity = arr.m_Capacity; //参数arr也是MyArray,所以可以直接访问自由属性 this->m_Size = arr.m_Size; this->pAddress = new int[arr.m_Capacity]; for (int i = 0; i < arr.m_Size; ++i) { this->pAddress[i] = arr.pAddress[i]; } } MyArray::~MyArray() { cout << "析构函数" << endl; if (NULL != this->pAddress) { delete [] this->pAddress; this->pAddress = NULL; } } void MyArray::insert(int val) { this->pAddress[this->m_Size] = val; this->m_Size++; } void MyArray::setData(int pos, int val) { this->pAddress[pos] = val; } int MyArray::getData(int pos) { return this->pAddress[pos]; } int MyArray::getCapacity() { return this->m_Capacity; } int MyArray::getSize() { return this->m_Size; } int &MyArray::operator[](int index) { return this->pAddress[index]; }
MyArrayTest.cpp
#include "MyArray.h" void test_array(){ MyArray arr; for (int i = 0; i < 10; ++i) { arr.insert(i + 1); } MyArray arr2(arr); arr2.setData(0, 1000); for (int i = 0; i < arr.getSize(); ++i) { cout << "arr[" << i << "] = " << arr.getData(i) << endl; } cout << endl; for (int i = 0; i < arr2.getSize(); ++i) { cout << "arr[" << i << "] = " << arr2.getData(i) << endl; } arr[1] = 10000; //因为返回的是引用,所以可以进行赋值的操作 cout << arr[1] << endl; } int main() { test_array(); return EXIT_SUCCESS; }
自定义字符串
MyString.h
#ifndef CPLUSPLUS_MYSTRING_H #define CPLUSPLUS_MYSTRING_H #include <iostream> using namespace std; class MyString { public: MyString(); MyString(const char *str); MyString(const MyString &str); ~MyString(); MyString &operator=(const MyString &str); MyString &operator=(const char *str); char &operator[](int index); MyString operator+(const MyString &str); MyString operator+(const char *str); private: friend ostream &operator<<(ostream &out, MyString &str); friend istream &operator>>(istream &in, MyString &str); char *pString; //维护在堆中开辟的字符数组 int m_Size; //字符串长度(不包含\0) }; #endif //CPLUSPLUS_MYSTRING_H
MyString.cpp
#include "MyString.h" #include <cstring> MyString::MyString() { new(this) MyString(new char[]{""}); } MyString::MyString(const char *str) { this->pString = new char[strlen(str) + 1]; strcpy(this->pString, str); this->m_Size = (int) strlen(str); } MyString::MyString(const MyString &str) { this->pString = new char(str.m_Size + 1); strcpy(this->pString, str.pString); this->m_Size = str.m_Size; } MyString::~MyString() { if (nullptr != this->pString) { delete[] this->pString; this->pString = nullptr; } } ostream &operator<<(ostream &out, MyString &str) { cout << str.pString; return out; } istream &operator>>(istream &in, MyString &str) { if (NULL != str.pString) { delete[] str.pString; str.pString = NULL; } char buf[1024]; cin >> buf; str.pString = new char[strlen(buf) + 1]; strcpy(str.pString, buf); str.m_Size = strlen(buf); return in; } MyString &MyString::operator=(const MyString &str) { if (NULL != this->pString) { delete[] this->pString; this->pString = NULL; } this->pString = new char(str.m_Size + 1); strcpy(this->pString, str.pString); this->m_Size = str.m_Size; return (*this); } MyString &MyString::operator=(const char *str) { return this->operator=(MyString(str)); } char &MyString::operator[](int index) { return this->pString[index]; } MyString MyString::operator+(const MyString &str) { return this->operator+(str.pString); } MyString MyString::operator+(const char *str) { int size = this->m_Size + strlen(str) + 1; char *temp = new char[size]; memset(temp, 0, size); strcat(temp, this->pString); strcat(temp, str); MyString res = temp; delete[] temp; //创建在堆中,所以要清掉 return res; }
MyStringTest
#include "MyString.h" void test() { MyString str1("abc"); //有参构造 MyString str2 = "def"; //有参构造 MyString str3(str1); //拷贝构造 cout << "重新输入str1的值:" << endl; cin >> str1; cout << "str1: " << str1 << endl; cout << "str2: " << str2 << endl; cout << "str3: " << str3 << endl; MyString str4; //无参构造 MyString str5; //无参构造 cout << "str4: " << str4 << endl; str4 = "opq"; str5 = str4; cout << "str5: " << str5 << endl; MyString *str6 = new MyString("xyz"); (*str6)[0] = 'X'; cout << "str6: " << (*str6) << endl; delete str6; MyString str7 = str1 + str2; MyString str8 = str7 + "XYZ"; cout << "str7: " << str7 << endl; cout << "str8: " << str8 << endl; } int main() { test(); return EXIT_SUCCESS; }
运算符重载
概述
c++
对运算符提供了重载函数的功能,格式为operator
关键字加上运算符,如加号运算符,重载函数为operator+
,这些重载函数可以简写为运算符进行运算,如operator+
可以简写为+
实现运算符重载的方式可以是全局函数,也可以是成员函数
operator+
- 成员函数重载
#include <iostream> using namespace std; class Score { private: int chinese; int math; int english; public: Score() {} Score(int chinese, int math, int english=60) : chinese(chinese), math(math), english(english) {} Score operator+(Score &score) { Score temp; temp.chinese = this->chinese + score.chinese; temp.math = this->math + score.math; temp.english = this->english + score.english; return temp; } int getChinese() { return this->chinese; } int getMath() { return this->math; } int getEnglish() { return this->english; } }; void test(){ Score score1(100, 90); Score score2(90, 80, 80); //Score score3 = score1.operator+(score2); Score score3 = score1 + score2; //简写形式 cout << "chinese: " << score3.getChinese() << endl; cout << "math: " << score3.getMath() << endl; cout << "english: " << score3.getEnglish() << endl; } int main(){ test(); return EXIT_SUCCESS; }
- 全局函数重载
#include <iostream> using namespace std; class Score { private: int chinese; int math; int english; public: Score() {} Score(int chinese, int math, int english=60) : chinese(chinese), math(math), english(english) {} void setChinese(int chinese) { this->chinese = chinese; } int getChinese() { return this->chinese; } int getMath() { return this->math; } void setMath(int math) { this->math = math; } int getEnglish() { return this->english; } void setEnglish(int english) { this->english = english; } }; Score operator+(Score score1, Score score2){ Score temp; temp.setChinese(score1.getChinese() + score2.getChinese()); temp.setMath(score1.getMath() + score2.getMath()); temp.setEnglish(score1.getEnglish() + score2.getEnglish()); return temp; } void test(){ Score score1(100, 90); Score score2(90, 80, 80); // Score score3 = operator+(score1, score2); Score score3 = score1 + score2; //简写形式 cout << "chinese: " << score3.getChinese() << endl; cout << "math: " << score3.getMath() << endl; cout << "english: " << score3.getEnglish() << endl; } int main(){ test(); return EXIT_SUCCESS; }
operator<< | operator>>
实现
cout << 类
,即直接打印一个类
- 成员函数重载
Class Person{ public: /* * 类比 p1.operator+(p2) => p1 + p2 * 得到 p1.operator<<(cout) => p1 << cout * 所以 这不是想要的效果(cout << p1),故而应采用全局函数方式 */ ostream& operator<<(Person & p) {} }
- 全局函数重载
#include <iostream> #include <string> using namespace std; class Person { friend ostream &operator<<(ostream &out, Person &p); private: string m_Name; int m_Age; public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } }; ostream &operator<<(ostream &out, Person &p) { out << "name=" << p.m_Name << "; age=" << p.m_Age; //name=张三; age=12 return out; } void test() { Person p("张三", 12); cout << p << endl; } int main() { test(); return EXIT_SUCCESS; }
operator++ | operator--
#include <iostream>
using namespace std;
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger& integer);
private:
int number = 0;
public:
/**
* 前置++
* @return 返回自身
*/
MyInteger& operator++(){
this->number++;
return *this;
}
/**
* 后置++,通过占位参数,c++会自动识别为后置++操作
* @return 返回新对象,这也是前置++效率更高的原因(会通过拷贝构造创建一个临时对象)
*/
MyInteger operator++(int) & {
MyInteger temp = *this;
this->number++;
return temp;
}
};
ostream& operator<<(ostream& out, MyInteger& integer) {
cout << integer.number;
return cout;
}
void test() {
MyInteger integer;
cout << integer << endl; //0
cout << ++(++integer) << endl; //2
cout << integer << endl; //2
integer++;
cout << integer << endl; //3
}
int main() {
test();
return EXIT_SUCCESS;
}
智能指针 & 指针运算符重载
#include<iostream>
using namespace std;
class Person{
private:
int m_Age;
public:
Person(int age) {
cout << "调用了Person的有参构造" << endl;
this->m_Age = age;
}
void showAge() const {
cout << "年龄为:" << this->m_Age << endl;
}
~Person() {
cout << "调用了Person的析构函数" << endl;
}
};
int main() {
test();
return EXIT_SUCCESS;
}
智能指针
- 在堆中创建的对象需要手动去调用delete释放
void test() { Person* p = new Person(18); p->showAge(); (*p).showAge(); delete p; }
- 使用智能指针自动释放
class SmartPoint{ private: Person* m_Person; public: SmartPoint(Person* p): m_Person(p){} ~SmartPoint() { if (NULL != this->m_Person) { delete this->m_Person; } } }; void test() { /* * 由于sp不是在堆中创建的,是在栈中创建的, * 所以函数test调用结束后会调用它的析构,而在它的析构中对创建的堆中对象Person进行了释放, * 从而实现了自动释放的作用 */ SmartPoint sp(new Person(18)); }
指针运算符重载
class SmartPoint{
private:
Person* m_Person;
public:
Person* operator->() {
return this->m_Person;
}
Person& operator*() {
return *(this->m_Person);
}
SmartPoint(Person* p): m_Person(p){}
~SmartPoint() {
if (NULL != this->m_Person) {
delete this->m_Person;
}
}
};
void test() {
/*
* 由于sp不是在堆中创建的,是在栈中创建的,
* 所以函数test调用结束后会调用它的析构,而在它的析构中对创建的堆中对象Person进行了释放,
* 从而实现了自动释放的作用
*/
SmartPoint sp(new Person(18));
sp->showAge(); //理论上应该是sp->->,不过编译器进行了优化
(*sp).showAge();
}
operator=
默认的实现是一个值的浅拷贝操作,这也是
Person p1; Person p2; p2 = p1
得以实现的原因,但是和拷贝构造一样,如果在析构中对对象进行了释放操作,也会存在重复删除,即后面的删除找不到地址的错误,此时就需要对operator=
进行重载了
#include <iostream>
#include <cstring>
using namespace std;
class Person{
friend void test();
private:
char* m_Name;
int m_Age;
public:
Person(char* name, int age): m_Age(age) {
this->m_Name = new char[strlen(name) + 1];
strcpy(this->m_Name, name);
}
Person(const Person& p) { //与本案例无关,只为健全代码
this->m_Name = new char[strlen(p.m_Name) + 1];
strcpy(this->m_Name, p.m_Name);
this->m_Age = p.m_Age;
}
Person& operator=(const Person& p){
if(nullptr != this->m_Name) { //先将可能存在的旧数据从堆中删除
delete [] this->m_Name;
this->m_Name = nullptr;
}
this->m_Name = new char[strlen(p.m_Name) + 1];
strcpy(this->m_Name, p.m_Name);
this->m_Age = p.m_Age;
return *this;
}
~Person() {
if(nullptr != this->m_Name) {
delete [] this->m_Name;
this->m_Name = nullptr;
}
}
};
void test() {
Person p1("张三", 19);//有参构造
Person p2("李四", 20);//有参构造
Person p3(p2);//拷贝构造
Person p4 = p3;//拷贝构造
Person p5("王五", 21);//有参构造
p1 = p2 = p3 = p4 = p5;
cout << "p1: " << p1.m_Name << " - " << p1.m_Age << endl;
cout << "p2: " << p2.m_Name << " - " << p2.m_Age << endl;
cout << "p3: " << p3.m_Name << " - " << p3.m_Age << endl;
cout << "p4: " << p4.m_Name << " - " << p4.m_Age << endl;
cout << "p5: " << p5.m_Name << " - " << p5.m_Age << endl;
// p1,p2,p3,p4,p5: 王五 - 21
}
int main() {
test();
return EXIT_SUCCESS;
}
operator[]
关系运算符重载
#include <iostream>
#include <string>
using namespace std;
class Person {
friend void test();
private:
string m_Name;
int m_Age;
public:
Person(string name, int age) : m_Name(name), m_Age(age) {}
bool operator==(Person &p) {
return this->m_Name == p.m_Name && this->m_Age == p.m_Age;
}
bool operator!=(Person &p) {
return !this->operator==(p);
}
};
void test() {
Person p1("张三", 20);
Person p2("李四", 21);
Person p3(p2);
cout << (p1 == p2) << endl; //0
cout << (p2 != p3) << endl; //0
}
int main() {
test();
return EXIT_SUCCESS;
}
operator()
#include <iostream>
using namespace std;
class MyPrint{
public:
void operator()(const string& str) {
cout << str << endl;
}
};
void test() {
MyPrint print;
print("abc"); //仿函数,本质是个对象,也叫函数对象
MyPrint()("def");
}
int main() {
test();
return EXIT_SUCCESS;
}
不要进行重载的符号
对于
&&
和||
,由于我们无法模拟短路特性的逻辑,如果对这两种符号进行重载会导致短路特性失效,最后得到错误的结果,所以最好不要对它们进行重载
模板
即类型参数化 - 泛型编程
函数模板
基本语法
#include <iostream>
using namespace std;
/**
* 1、使用template关键字定义模板
* 2、用class关键字也可以,不过一般函数模板用typename,类模板用class
* 3、必须是紧跟着的函数才能使用typename声明的T
*/
template<typename T>
void mySwap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
void test() {
int a = 10;
int b = 20;
/*
* 方式一:自动类型推导
* 注意:类型必须一致
*/
//mySwap(a, b);
/*
* 方式二:显示指定类型
* 注意:模板函数的参数需为&(引用),否则传入char类型也可以,因为它可以被隐式转为int类型
*/
mySwap<int>(a, b);
cout << "a=" << a << "; b=" << b <<endl;
}
int main() {
test();
return EXIT_SUCCESS;
}
模板函数也可以重载
template<typename T>
T test(T a, T b) {
//...
}
template<typename T>
T test(T a, T b, Tc) {
//...
}
模板函数和普通函数同时可被调用时
- 优先调用普通函数
如果同时可以被模板函数和普通函数调用,即使普通函数只有声明、没有定义
template<typename T> void test(T a, T b) { cout << "模板函数调用" << endl; } void test(int a, int b) { cout << "普通函数调用" << endl; } test(1, 2); //"普通函数调用"
- 强制调用模板函数
可以使用空模板参数列表来强制转为调用模板函数
template<typename T> void test(T a, T b) { cout << "模板函数调用" << endl; } void test(int a, int b) { cout << "普通函数调用" << endl; } test<>(1, 2); //"模板函数调用"
- 如果函数模板有更好的匹配则优先用函数模板
template<typename T> void test(T a, T b) { cout << "模板函数调用" << endl; } void test(int a, int b) { cout << "普通函数调用" << endl; } char a = 'a'; char b = 'b'; test(a, b); //模板函数调用
函数模板具体化
如一个进行两个数据
==
号操作的模板函数,对于自定义类型是无法实现的,此时可以通过具体化技术,为自定义类型单独写一个特殊的函数模板
#include <iostream>
using namespace std;
class Person {
public:
string m_Name;
int m_Age;
Person(string name, int age): m_Name(name), m_Age(age) {}
};
template<typename T>
bool compare(T &a, T &b) {
cout << "函数模板调用" << endl;
return a == b;
}
/*
* 具体化技术:在函数前添加template<>
* 具体化技术:可以省略模板声明
*/
template<> bool compare(Person &a, Person &b) {
cout << "具体化技术" << endl;
return a.m_Name == b.m_Name && a.m_Age == b.m_Age;
}
void test() {
Person p1("小明", 19);
Person p2("小明", 19);
bool res = compare(p1, p2); //具体化技术,会自动调用对应的特殊模板
cout << "res = " << res << endl; //1
}
int main() {
test();
return EXIT_SUCCESS;
}
类模板
基本语法
#include <iostream>
#include <string>
using namespace std;
/**
* 1、模板中可以同时声明多个关键字,如这的N和A(函数模板也可以)
* 2、类模板可以设置默认类型值,如这的A = int(函数模板不可以)
* 3、类模板不可以使用自动类型推导,必须显示指定类型
*/
template<class N, class A = int>
class Person {
public:
Person(N name, A age) {
this->m_Name = name;
this->m_Age = age;
}
N m_Name;
A m_Age;
void show() {
cout << "name=" << this->m_Name << ", age=" << this->m_Age << endl;
}
};
void test() {
//Person p("", 1); //报错,必须显示指定类型
Person<string> p1("张三", 21); //由于设置了默认值,所以可以进行省略
Person<string, string> p2("孙悟空", "1000");
p1.show();
p2.show();
}
int main() {
test();
return EXIT_SUCCESS;
}
类模板做函数参数
一、指定参数类型
void showInfo(Person<string, int> &p) {
p.show();
}
void test() {
Person<string, int> p("孙悟空", 1000);
showInfo(p);
}
二、参数模板化
template<typename T1, typename T2>
void showInfo(Person<T1, T2> &p) {
p.show();
}
void test() {
Person<string, int> p("孙悟空", 1000);
showInfo(p);
}
三、类模板化
template<typename T>
void showInfo(T &p) {
p.show();
}
void test() {
Person<string, int> p("孙悟空", 1000);
showInfo(p);
}
类模板与继承
一、指明父类泛型
必须指明类型,否则无法给子类分配内存(即不知道要分配多少内存)
template<class T>
class Father {
public:
T m; //即使这里没有用到T,子类也要指定泛型
};
class Son: public Father<string> {
//...
};
二、继续使用泛型
template<class T>
class Father {
public:
T m_n;
Father(T n): m_n(n) {}
};
template<class T1, class T2>
class Son: public Father<T1> {
public:
T2 m_a;
Son(T1 m, T2 a): Father<T1>(m), m_a(a) {}
};
void test() {
Son<string, int> son("孙悟空", 10000);
}
类模板成员函数的类外实现
template<class N, class A = int>
class Person {
public:
Person(N name, A age);
N m_Name;
A m_Age;
void show();
};
/**
* 1、必须再次声明模板
* 2、作用域必须指定泛型,即使函数未用到
*/
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
template<typename T1, typename T2>
void Person<T1, T2>::show() {
cout << "name=" << this->m_Name << ", age=" << this->m_Age << endl;
}
类模板成员函数的分文件实现
person.hpp
#ifndef COURSE_DEMO_HPP #define COURSE_DEMO_HPP #include <iostream> #include <string> using namespace std; template<class N, class A = int> class Person { public: Person(N name, A age); N m_Name; A m_Age; void show(); }; /** * 1、必须再次声明模板 * 2、作用域必须指定泛型,即使函数未用到 */ template<typename T1, typename T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } template<typename T1, typename T2> void Person<T1, T2>::show() { cout << "name=" << this->m_Name << ", age=" << this->m_Age << endl; } #endif //COURSE_DEMO_HPP
test.cpp
#include "demo.hpp" void test() { Person<string, int> p("孙悟空", 100000); p.show(); } int main() { test(); return EXIT_SUCCESS; }
- 说明
如果将person的成员函数的类外实现放到
person.cpp
文件中,而自身的声明放入person.h
。
由于类模板的代码是在运行期确定的,而test.cpp
中引入的是person.h
,就会导致无法链接到类外实现的函数而报错
此时可以让test.cpp
直接引入person.cpp
,因为这里可以直接找到函数
但是按业界规范,一般不会去直接引入cpp
文件,且一般也不会对类模板的成员函数进行类外实现,而是直接类内实现
如果一定要类外实现,一般的做法就是都放到一个文件中,然后将这种头文件与具体实现放一起的文件修改为hpp
后缀的文件
类模板与友元函数
类内实现
#include <iostream>
#include <string>
using namespace std;
template<class N, class A = int>
class Person {
private:
N m_Name;
A m_Age;
public:
Person(N name, A age) {
this->m_Name = name;
this->m_Age = age;
}
friend void show(Person<N, A> &p) {
cout << "name=" << p.m_Name << ", age=" << p.m_Age << endl;
}
};
void test() {
Person<string, int> p("孙悟空", 1000);
show(p);
}
int main() {
test();
return EXIT_SUCCESS;
}
类外实现一
#include <iostream>
#include <string>
using namespace std;
//4.需要让3的show知道Person类的存在
template<class N, class A>
class Person;
//3.需要让Person知道函数show的存在
template<typename N, typename A>
void show(Person<N, A> &p);
template<class N, class A = int>
class Person {
private:
N m_Name;
A m_Age;
public:
Person(N name, A age) {
this->m_Name = name;
this->m_Age = age;
}
//2.通过空模板参数列表<>,告知编译器这是个模板函数,而不是普通函数
friend void show<>(Person<N, A> &p);
};
//1.定义模板
template<typename N, typename A>
void show(Person<N, A> &p) {
cout << "name=" << p.m_Name << ", age=" << p.m_Age << endl;
}
void test() {
Person<string, int> p("孙悟空", 1000);
show(p);
}
int main() {
test();
return EXIT_SUCCESS;
}
类外实现二
#include <iostream>
#include <string>
using namespace std;
//2.需要让1的show知道Person类的存在
template<class N, class A>
class Person;
//1.定义模板
template<typename N, typename A>
void show(Person<N, A> &p) {
cout << "name=" << p.m_Name << ", age=" << p.m_Age << endl;
}
template<class N, class A = int>
class Person {
private:
N m_Name;
A m_Age;
public:
Person(N name, A age) {
this->m_Name = name;
this->m_Age = age;
}
//3.通过空模板参数列表<>,告知编译器这是个模板函数,而不是普通函数
friend void show<>(Person<N, A> &p);
};
void test() {
Person<string, int> p("孙悟空", 1000);
show(p);
}
int main() {
test();
return EXIT_SUCCESS;
}
类型转换
静态转换
父子类之间指针或引用的转换
class Base {
};
class Child : public Base {
};
void test() {
Base *base = nullptr;
Child *child = nullptr;
//父转子:不安全
Child *c = static_cast<Child *>(base);
//子转父:安全
Base *b = static_cast<Base *>(child);
}
基本类型之间的转换
void test() {
char c = 'a';
double d = static_cast<double>(c);
cout << d << endl; //97
}
动态转换
一般用于类层次之间的上下行转换
上行转换:此时动态转换dynamic_cast
效果同静态转换static_cast
下行转换:具有类型检查功能,比static_cast
更安全
下行转换默认不允许
void test() {
Base *base = nullptr;
Child *child = nullptr;
//父转子:不安全
//Child *c = dynamic_cast<Child *>(base); //报错
}
如果存在多态则允许下行转换
多态中,类型转换总是安全的
class Base {
virtual void func() {}
};
class Child : public Base {
void func() {};
};
void test() {
/**
* 多态:
* 1、继承
* 2、同名虚函数
* 4、父类指针指向子类对象
*/
Base *base = new Child;
Child *c = dynamic_cast<Child *>(base); //不报错
}
常量转换
常量指针转换成非常量指针
void test() {
//移除const
const int* p1 = nullptr;
int* np1 = const_cast<int*>(p1);
//转为const
int *p2 = nullptr;
const int* np2 = const_cast<const int*>(p2);
//禁止对非指针或非引用的变量,使用const_cast转换来直接移除掉它的const
//const int a = 10;
//int na = const_cast<int>(a); //报错
}
常量指针转换成非常量引用
void test() {
int n1 = 10;
//转为引用
int & rn1 = n1;
//非常量引用转为常量引用
const int &rn2 = const_cast<const int&>(rn1);
}
重新解释转换
关键字:
reinterpret_cast
主要用于将一种数据类型转成另一种类型,可以将指针转成一个整数,也可以将一个整数转成指针,是个不安全的转换操作,一般不使用
异常
c++
中的异常必须处理,否则程序会自动调用terminate
函数,使得程序运行中断
基本语法
#include <iostream>
using namespace std;
class MyException {
public:
void printError() {
cout << "除数为0异常" << endl;
}
};
int division(int a, int b) {
if (0 == b) {
//throw 'c';
//throw 1.12;
throw MyException();
}
return a / b;
}
void test() {
try {
division(10, 0);
} catch (int) { //捕获int类型的异常
cout << "int 类型的异常" << endl;
} catch (double) {
throw; //继续抛出异常
} catch (char c) { //可以接收异常值
cout << "char 类型的异常:" << c << endl;
} catch (MyException e) {
e.printError();
} catch (...) {
cout << "其他 类型的异常" << endl;
}
}
int main() {
try {
test();
} catch (double) {
cout << "double 类型的异常" << endl;
}
return EXIT_SUCCESS;
}
栈解旋
指在
throw
抛出异常之前,会将其所处的方法栈中的(非堆)对象进行清除,这个操作称之为栈解旋
#include <iostream>
#include <string>
using namespace std;
class MyException {
public:
void printError() {
cout << "除数为0异常" << endl;
}
};
class Demo {
private:
string m_Flag;
public:
Demo(string flag): m_Flag(flag) {
cout << "Demo的构造函数被调用:" << m_Flag << endl;
}
~Demo() {
cout << "Demo的析构函数被调用:" << m_Flag << endl;
}
};
int division(int a, int b) {
Demo d1("d1");
if (0 == b) {
Demo d2("d2");
throw MyException();
}
return a / b;
}
void test() {
try {
/**
* Demo的构造函数被调用:d1
* Demo的构造函数被调用:d2
* Demo的析构函数被调用:d2
* Demo的析构函数被调用:d1
* 除数为0异常
*/
division(10, 0);
} catch (MyException e) {
e.printError();
}
}
int main() {
test();
return EXIT_SUCCESS;
}
异常接口声明
用于限定函数可能抛出的异常的类型有哪些
class MyException {
public:
void printError() {
cout << "除数为0异常" << endl;
}
};
/**
* 表示只允许抛出int、MyException类型的异常
* 如果为空,即throw(),表示不允许抛出异常
*/
void func() throw(int, MyException) {
throw MyException();
}
void test() {
try {
func();
} catch (MyException e) {
e.printError();
} catch (...) {
cout << "other exception !" << endl;
}
}
异常变量的抛出与接收
class MyException {
public:
MyException() {
cout << "MyException的默认构造" << endl;
}
MyException(const MyException &e) {
cout << "MyException的拷贝构造" << endl;
}
~MyException() {
cout << "MyException的析构函数" << endl;
}
void printError() {
cout << "除数为0异常" << endl;
}
};
方式一
/**
* 抛出:MyException()
* 接收:MyException e
* 结果:
* 1、MyException的默认构造
* 2、MyException的拷贝构造
* 3、除数为0异常
* 4、MyException的析构函数
* 5、MyException的析构函数
*/
void func() {
throw MyException();
}
void test() {
try {
func();
} catch (MyException e) { //值传递,会调用拷贝构造创建一个新对象
e.printError();
}
}
方式二
推荐
/**
* 抛出:MyException()
* 接收:MyException &e
* 结果:
* 1、MyException的默认构造
* 2、除数为0异常
* 3、MyException的析构函数
*/
void func() {
throw MyException();
}
void test() {
try {
func();
} catch (MyException &e) { //通过引用传递,不会创建新对象,效率会高些
e.printError();
}
}
方式三
/**
* 抛出:&MyException()
* 接收:MyException *e
* 结果:
* 1、MyException的默认构造
* 2、MyException的析构函数
* 3、除数为0异常
*/
void func() {
throw &MyException();
}
void test() {
try {
func();
} catch (MyException *e) {
cout << "自定义异常" << endl;
//e->printError(); 对象被提前释放,无法对其进行调用
}
}
方式四
/**
* 抛出:new MyException()
* 接收:MyException *e
* 结果:
* 1、MyException的默认构造
* 2、除数为0异常
* 3、MyException的析构函数
*/
void func() {
throw new MyException();
}
void test() {
try {
func();
} catch (MyException *e) {
e->printError();
delete e; //需要手动释放
}
}
异常与多态
class BaseException{
public:
virtual void printError() = 0;
};
class NullPointerException: public BaseException {
public:
void printError() override {
cout << "空指针异常" << endl;
}
};
class OutOfRangeException: public BaseException {
public:
void printError() override {
cout << "数组下标越界异常" << endl;
}
};
void func() {
//throw NullPointerException();
throw OutOfRangeException();
}
void test() {
try{
func();
}catch(BaseException &e) {
e.printError();
}
}
系统标准异常
异常类 | 异常说明 |
---|---|
exception | 所有标准异常类的基类 |
bad_alloc | 当 operator new 和 operator new[]请求分配内存失败时 |
bad_exception | 特殊的一个异常,如果函数抛出了非 指定的异常抛出列表 中的异常,不论什么类型,都会被替换为bad_exception |
bad_typeid | 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类时 |
bad_cast | 使用dynamic_cast转换失败时 |
ios_base::failure | io操作过程出现错误 |
logic_error | 逻辑错误,可以在运行前检测的错误 |
runtime_error | 运行时错误,仅在运行时才可以检测的错误 |
logic_error的子类 | |
length_error | 试图生成一个超出该类型最大长度的对象,如vertor的resize操作 |
demain_error | 参数的值域错误,主要发生在数学函数中,例如一个负值去调用了一个只能操作非负数的函数 |
out_of_range | 超出有效范围,如数租下标越界 |
invalid_argument | 参数不合适,如利用string对象构造bitset时,string中的字符不是'0'或'1'的话,抛出该异常 |
runtime_error的子类 | |
range_error | 计算结果超出了有意义的值域范围 |
overflow_error | 算术计算上溢 |
underflow_error | 算术计算下溢 |
#include <iostream>
#include <stdexcept> //需要导入异常库
using namespace std;
void func(int n) {
if (n < 10) {
throw out_of_range("数值必须大于等于10");
}
}
void test() {
try{
func(1);
} catch (out_of_range &e) { //一般用多态的方式,即exception去接收
cout << e.what() << endl;
}
}
int main() {
test();
return EXIT_SUCCESS;
}
扩展系统异常
#include <iostream>
#include <string>
using namespace std;
class MyOutOfRangeException : public exception {
private:
string m_Message;
public:
MyOutOfRangeException(char *str) {
this->m_Message = str; //char *可以直接转为string(反之则不行)
}
//重写时const和异常范围不能丢
const char * what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW {
return this->m_Message.c_str(); //string转char *
}
};
void func(int n) {
if (n < 10) {
throw MyOutOfRangeException("数值必须大于等于10");
}
}
void test() {
try {
func(1);
} catch (exception &e) { //一般用多态的方式,即exception去接收
cout << e.what() << endl;
}
}
int main() {
test();
return EXIT_SUCCESS;
}
IO
继承关系图
四种流对象
对象 | 含义 | 对应设备 | 对应类 | c语言中响应的标准文件 |
---|---|---|---|---|
cin | 标准输入流 | 键盘 | instream_withassign | stdin |
cout | 标准输出流 | 屏幕 | onstream_withassign | stdout |
cerr | 标准错误流 | 屏幕 | onstream_withassign | stderr |
clog | 标准日志流 | 屏幕 | onstream_withassign | stderr |
标准输入流
get()
等待输入:等待缓冲区中有数据读
void test() {
/**
* 无参:一次只能读取一个字符
* 输入:as
* 输出:c1=a, c2=s, c3=换行符, c4=等待输入
*/
char c1 = cin.get();
char c2 = cin.get();
char c3 = cin.get();
char c4 = cin.get();
cout << "c1=" << c1 << ", c2=" << c2 << ", c3=" << c3 << ", c4=" << c4 << endl;
/**
* 一参:将读取的一个字符放入参数中
* 输入:a
* 输出:c=a
*/
char c;
cin.get(c);
cout << "c=" << c << endl;
/**
* 两参:将缓冲区数据都放入第一个参数,最多放第二个参数个
* 注意:换行符不会被放入第一个参数,会遗留在缓冲区中,需要再手动调用一次 cin.get() 来清空缓冲区
* 输入:hello world
* 输出:hello w
*/
char buf1[24] = {0};
cin.get(buf1, 8);
cout << "buf1=" << buf1 << endl;
/**
* 两参:换行符不会被放入第一个参数,会遗留在缓冲区中,需要再手动调用一次 cin.get() 来清空缓冲区
* 输入:hello world
* 输出:
* buf2=hello world
* true
*/
char buf2[24] = {0};
cin.get(buf2, 24);
cout << "buf2=" << buf2 << endl; //buf2=hello world
char n = cin.get();
cout << (n == '\n'? "true" : " false") << endl; //true
}
getline()
用法同
get()
的两参的情况
它们都不会将换行符放入第一个参数中,
不过不同的是getline()
会自动将换行符清理丢弃,而不是遗留在缓冲区中
ignore()
忽略前n个字符,n可指定,不指定则默认为1
void test() {
/**
* 无参:默认忽略一个字符
* 输入:hello
* 输出:c1=e
*/
cin.ignore();
char c1 = cin.get();
cout << "c1=" << c1 << endl;
/**
* 有参:如5,忽略5个字符
* 输入:hello
* 输出:c2=换行符
*/
cin.ignore(5);
char c2 = cin.get();
cout << "c2=" << c2 << endl;
}
peek()
从缓冲区中拷贝出数据,而不是直接取走缓冲区的数据
void test() {
/**
* 无参:从缓冲区中拷贝出一个字符
* 输入:hel
* 输出:c1=e, c2=e, c3=l
*/
cin.ignore();
char c1 = cin.peek();
char c2 = cin.get();
char c3 = cin.get();
cout << "c1=" << c1 << ", c2=" << c2 << ", c3=" << c3 << endl;
}
putback()
将字符放回上次获取数据的位置(字符不一定来自缓冲区)
void test() {
/**
* 一参:将该参数原位置放回缓冲区
* 输入:hello
* 输出:buf1=hello
*/
char c = cin.get();
cin.putback(c);
char buf1[1024] = {0};
cin.getline(buf1, 1024);
cout << "buf=" << buf1 << endl;
/**
* 一参:参数可以是任意的字符,不一定要来自缓冲区
* 输入:hello
* 输出:buf1=allo
*/
cin.ignore();
cin.get();
cin.putback('a');
char buf2[1024] = {0};
cin.getline(buf2, 1024);
cout << "buf=" << buf2 << endl;
}
>>
将输入放入变量中
void test() {
/**
* 输入:123
* 输出:123
*/
char c = cin.peek();
if (c >= '0' && c <= '9') {
int num;
cin >> num;
cout << num << endl;
}
}
fail() & clear() & sync()
fail()
:缓冲区是否异常(异常标识位),0否,1是
clear()
:清空缓冲区
sync()
:重置异常标识位为0
标准输出流
flush()
刷新缓冲区(
linux
下有效)
void test() {
cout << "hello world";
cout.flush();
}
put()
向缓冲区中写入字符
void test() {
//向屏幕中输出hello
cout.put('h').put('e').put('l').put('l').put('o');
}
write()
向屏幕中输出字符串
第一个参数为要输出的内容,第二个参数为输出的最多字符个数
void test() {
char buf[] = "hello world";
cout.write(buf, (int)strlen(buf)); //hello world
}
格式化输出
方式一:通过流成员函数
void test() {
int number = 99;
/**
* 作用:指定宽度为20个字符
* 输出: 99(18个空格99)
*/
cout.width(20);
/**
* 作用:指定填充符号
* 输出:******************99(18个*99)
*/
cout.fill('*');
/**
* 作用:左对齐 - 值放左边(默认放右边)
* 输出:99******************
*/
cout.setf(ios::left);
/**
* 作用:卸载10进制
*/
cout.unsetf(ios::dec);
/**
* 作用:安装16进制
* 前提:需要先卸载掉原来的10进制
* 输出:63******************
*/
cout.setf(ios::hex);
/**
* 作用:显示基数(进制前缀)
* 输出:0x63****************
*/
cout.setf(ios::showbase);
/**
* 作用:卸载16进制
*/
cout.unsetf(ios::hex);
/**
* 作用:安装8进制
* 输出:0143****************
*/
cout.setf(ios::oct);
cout << number << endl; //0143
}
方式二:通过控制符
需要引入头文件:
#include<iomanip>
void test() {
int number = 99;
cout << setw(20) //同width(20)
<< setfill('*') //同fill('*')
<< setiosflags(ios::left) //同setf(ios::left);
<< hex //同unsetf(ios::dec) + setf(ios::hex)
<< setiosflags(ios::showbase) //同setf(ios::showbase)
<< number
<< endl;
}
文件读写
头文件:
#include <fstream>
继承关系图
文件打开方式
ios::in
以输入方式打开
ios::out
以输出方式打开(默认),文件不存在则自动创建,会覆盖源文件内容
ios::app
以输出方式打开,文件不存在则自动创建,数据以追加形式写入
ios::ate
以输出方式打开,文件不存在则自动创建,文件指针指向文件末尾
ios::trunc
打开一个文件,如果文件已存在则删除文件数据,如果文件不存在则创建该文件
ios::binary
以二进制方式打开文件(默认ASCII方式),文件不存在则自动创建,
写数据到文件
#include <fstream>
void test() {
//ofstream ofs("./test.txt", ios::out); //直接在构造对象时指定路径和打开方式
ofstream ofs;
ofs.open("./test.txt", ios::out|ios::trunc); //可同时制定多个打开方式
cout << ofs.is_open() << endl;
if (ofs.is_open()) { //是否成功打开
ofs << "hello" << endl;
ofs << "world" << endl;
ofs.close(); //关闭流
}
}
读取文件数据
方式一:<<
一次读取一行数据
#include <fstream>
void test() {
ifstream ifs("./test.txt", ios::in);
if (ifs.is_open()) {
char buf[1024] = {0};
while (ifs >> buf) { //一次读取一行数据
cout << "当前buf:" << buf << endl;
}
ifs.close(); //关闭流
}
}
方式二:getline
一次读取一行数据
#include <fstream>
void test() {
ifstream ifs("./test.txt", ios::in);
if (ifs.is_open()) {
char buf[1024] = {0};
while (ifs.getline(buf, sizeof(buf))) { //一次读取一行数据
cout << "当前buf:" << buf << endl;
}
ifs.close(); //关闭流
}
}
方式三:全局getline
一次读取一行数据
#include <fstream>
void test() {
ifstream ifs("./test.txt", ios::in);
if (ifs.is_open()) {
string buf;
while (getline(ifs, buf)) { //一次读取一行数据
cout << "当前buf:" << buf << endl;
}
ifs.close(); //关闭流
}
}
方式四:get
一次读取一行个字符
#include <fstream>
void test() {
ifstream ifs("./test.txt", ios::in);
if (ifs.is_open()) {
char c;
while ((c = ifs.get()) != EOF) { //一次读取一行数据
cout << "当前c:" << c << endl;
}
ifs.close(); //关闭流
}
}