C/C++
C++
1. c++
1.1 c++特征
# c++ 是编译型语言 编译后通过链接 整合其他依赖库 生成可执行文件 以后再运行 无需编译 执行效率高
# c++ 支持面向对象编程 同时处理运行速度块
# c++ 主要组成部分:核心语言(提供所有构建:变量 数据类型) + c++标准库(提供函数用于操作文本 字符串等) + 标准模块库(提供方法 用于操作数据结构等)
1.2 c++环境安装
#window10 安装c++编译器(MinGw, Cygwin)
MinGw下载:https://nchc.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe
配置系统环境变量:Path--->x:/mingw/bin
测试环境: g++ -v
#Linux(ubuntu)
sudo apt update # 更新资源包列表
sudo apt install bulid-essential # 安装bulid-essential软件包(gcc, g++等)
g++ -v(gcc:9.3.0)
#相关的开发工具
Visual Studio:https://visualstudio.microsoft.com/zh-hans/downloads/
Clion: https://www.jetbrains.com/clion/download/
JetBrains所有软件破解补丁:https://pan.baidu.com/s/1rRMi2k3NelLmE1-89xM4Mg 提取码:u3om


1.3 c++ 实例
//---->main.cpp
// 导入头文件-input output stream 输入输出流[python:外部的模块]
#include<iostream>
// 声明命名空间std
using namespace std;
//携带参数 int main(int arg )
int main(){
// 把打印对象交给cout对象
// std::endl 表示结束 会有换行,刷新缓冲区数据
std::cout << "hello c++" << std::endl;
// 函数执行结束的状态码
return 0;
}
//小结:
//1.g++ main.cpp 直接编译成一个可执行文件(exe)
//2.;分号是结束语句标识
//3.int _test; :至少要有空格 用于编译器编译区分
//4.int main{}: 主函数入口
//5.<<运算符用于向屏幕传多个值
//6. std 命名空间 决定函数 类 变量作用域 可以使用namespace 进行声明
//单行注释
/*多行注释*/
2. 变量
2.1 变量命名
//标识符用于识别变量&函数&类&模块
//标识符(变量)通常^[A-Z a-Z _] 且区分大小写
//C++关键字:https://www.runoob.com/w3cnote/cpp-keyword-intro.html
2.2 基本数据类型
| Data Type | Memory(byte) | Format Specifier |
|---|---|---|
| int(整形) | 4 | %d |
| char(单个字符型) | 1 | %c |
| float(浮点型) | 4 | %f |
| double(双浮点型) | 8 | %lf |
| bool(布尔类型) | 1 | - |
| string(多个字符) | 32 | %s |
//修饰符类型(添加在数据类型前)
//signed(有符号) unsigned long(双精度) short
//eg:unsigned a; <=> unsigned int a;
2.3 数据类型重命名
//通过typedef声明(对数据类型取别名)
typedef int bb;
bb length; <-> int length;
2.4 关键字
//1.const声明常量(初始化一次)
const int aa = 11;
//常量固定值 可以是任何基本数据类型(int float char string bool) 在定义后不可进行修改 否则会出现编译错误
// 2.define 预处理 编译前执行
#define aa 11
//3.扩展c++11 auto关键字
auto i = 10; // 自动匹配合适的变量类型
//4.typedef type name重命名数据类型
typedef int i;
int number;<-> i number;
//5.static修饰符 (默认变量可在其他源文件使用使用static后只能在本源文件使用)
static int a;
//整数常量可携带后缀U(unsigned) L(long)
// 浮点型可携带后缀e | E(指数类型)
//6.override 重写
class A{
public:
virtual void test(){}
};
class B:public A{
void test() override{}
};
//继承情况下用于检查1.父类函数是否为虚函数 2.子类函数是否重写父类函数
//7.final
class A final{};
// class B: public A{}; # 编译错误 final修饰的类 限制被子类继承
class C{
virtual void test() final{}
};
class D{
// void test(){} # 编译错误 final修饰的虚函数 限制被子类重写
}
2.4.1 const
#include <iostream>
using namespace std;
class v{
public:
const int x=1; // 常量成员属性 不可再修改
//mutable int y; // mutable修饰符属性 使常函数 常对象 可以修改成员属性值
int y;
int z;
//构造函数
v(){
cout << "无参构造"<< endl;
}
void test(){
//this: v*
//x =3; // 编译错误
y=2, z=3;
cout << x << " " << y << " " << z << endl;
cout << "test...."<< endl;
}
// 常函数(对成员数据修改权限管理)
void test1() const{
//this: const* v
// 1.不能修改成员属性数据 2.实则是修饰的this指针
//y=2, z=3; //编译错误
//test(); // 常函数默认只能调用常函数
test2();
cout << "test1...."<< endl;
}
void test2() const{
cout << "test2...."<< endl;
}
};
int main(){
// 常对象
const v v1; // 需要提供自定义构造函数
v1.x; // 可获取成员属性数据、
v1.test1(); // 可调用对象函数
//v1.y = 55; // 默认不可对成员属性修改
//v1.y = 55; // 常函数&&常对象修改成员属性可使用关键字mutable 修饰成员属性
}
//常函数
//1.函数内部默认不可修改成员
//2.默认只可调用常函数
//3.所有对象均可调用常函数
//常对象
//1.默认只能调用常函数
//2.默认不可修改成员
//使用mutable 修饰符修饰成员函数 常对象常函数可对成员修改
2.4.2 auto
#include <iostream>
using namespace std;
int main(){
// 关键字const
const int a = 11; // const 修饰符修饰的变量只能初始化一次 不可再进行修改
//a = 22;// 报错
//关键字auto
auto i = 10; // auto 修饰符修饰的变量自动匹配变量类型
cout << i << endl; // 10
}
2.4.3 static
2.4.3.1 变量-static
#include <iostream>
using namespace std;
static int a = 22; // 静态类型变量初始化
int t1(){
static int n = 0;
n ++;
cout << "n1=" << n << "\t";
return n;
}
int t2(){
int n = 0;
n ++;
cout << "n2=" << n << "\t";
return n;
}
int main(){
//关键字static
cout << "静态全局变量a=" << a << endl;
t1();
t1();
t1();
t2();
t2();
t2();
// n1=1 n1=2 n1=3
// n2=1 n2=1 n2=1
}
//默认变量函数类均可在其他源文件使用(extern)
//使用static 修饰符只能在本源文件使用
/* static修饰局部变量 只能在本源文件使用
* 只初始化一次 保存在全局数据区 而非栈内存
* 再调用值会是之前调用的结果
*
* 静态全局变量 相较于全局变量仅仅是只能在本地源文件使用
* 函数类型加static 类似作用
*/
2.4.3.2 类-static
#include <iostream>
using namespace std;
class food{
public:
static string name; // 静态成员变量
//static string name="xyz"; //编译错误
int price;
food(int price):price(price){
cout << "构造函数"<< endl;
}
//静态成员函数
static void test(){
//cout << price << endl; // 不能访问普通成员
cout<< name << endl; // 只能访问静态成员(函数 属性)
cout << "test..."<< endl;
}
//普通成员函数
void test1(){
test(); // 能调用静态成员函数:对象已创建
cout <<"test1..."<<endl;
}
};
string food::name="xyz"; // 静态成员变量初始化
int main(){
food f(11);
//cout << f.name << endl; // 不可直接对静态成员属性访问 需要初始化
cout<<"静态成员属性初始化后---------------------"<< endl;
cout<< f.name << endl;
cout << f.price<< endl;
cout << "------------------------------------"<< endl;
food::test(); // 类名访问成员
f.test(); // 使用对象访问成员
}
/* 静态成员属性
* 1.不可在类中修改
* 2.在类的外部进行 静态成员初始化才可访问
* 静态成员函数
* 内部只能访问静态成员(函数 属性) 没有this指针
* 静态成员不需要创建对象也可访问 通过类名::静态成员
* 非静态成员需要创建对象 才能访问(静态&&非静态) 这些成员会分配到指定空间
*/
2.4.4 move
#include <iostream>
using namespace std;
int cal(int&& x, int && y){
cout << x*y << endl;
return x*y;
}
int main(){
int x=3, y=2;
//cal(x, y); //必须接收右值、
cal(3, 2); // 6
cal(move(x), move(y)); // 6
}
/* move函数
* 1.使左值转换成右值
* 2.对已有对象的数据转移到新对象时触发移动构造函数
*/
2.4.5 this
#include <iostream>
using namespace std;
class cls{
public:
int id;
cls(){
cout << "无参构造"<< endl;
}
cls(int id){
this->id = id;
cout << "有参构造"<< endl;
}
void test(){
cout << "test...."<< endl;
}
//链式调用
cls test1(){
cout << "test1...."<< endl;
return *this; // 返回当前对象
}
cls test2(){
cout << "test2...."<< endl;
return *this;
}
~cls(){
cout << "析构"<< endl;
}
};
int main(){
//this指针
cls c;
//1.普通调用
c.test();
//2.链式调用
c.test1().test2();
}
/* this指针
* 1.表示当前对象的指针
* 2.在类成员函数可以使用this指针
* 3.用于在构造函数内部区分同名的变量(参数与成员属性名相同)
* 4.链式调用时用于返回对象本身
*/
2.5 输入输出
2.5.1 一般输入输出
// 一般格式输入输出cout&&cin
#include <iostream>
using namespace std;
int main(){
// 输入(通常和输入同时使用)
cout<< "输入姓名:" << endl;
string name;
cin >> name; // cin关键字输入
// 输出
cout << "姓名:" << name << endl;
//1.布尔类型
bool n = false;
cout << "n输出:" << n << endl;
cout << "n输出:" << boolalpha << n << endl;
cout << "----------------------------------"<< endl;
//2.整形
int n1 = 10;
cout << "十进制:" << dec << n1 <<endl;
cout << "八进制:" << oct << n1 <<endl;
cout << "十六进制:" << hex << n1 <<endl;
cout << "----------------------------------"<< endl;
//3.浮点型(精确度 自动四舍五入)
float pi = 3.14159263;
cout << setprecision(4) << pi <<endl;
return 0;
}
// 小结:cin&cout读取的是数据流 而非具体数据 输入的数据优先存入缓冲区 再从缓冲区输出
//scanf格式化输入: scanf(% + 格式选项 + 格式转换符, 变量地址(&变量))
// printf格式化输出: % + 格式选项 + 格式转换符
/* 格式选项
* w 输出的占位 若大于w 则实际输出
* - 左对齐 默认右对齐
* + 输出符号(+ -) 默认只输出-
* # 八(十)进制的值非0 输出 格式(0x|0)
* .p 输出精度 浮点数由p决定小数点后保留的数 字符串 左对齐输出p个字符
*/
/* 格式转换符(eg:%D)
* d 有符号的十进制整形(<=>%i)
* u 无符号十进制整形
* o 无符号八进制
* x 无符号十六进制(小写)
* X 无符号十六进制(大写)
* c 单个字符('a')
* f 以浮点型输出
* e 以科学计数法形式输出浮点型(小写)
* s char*字符串输出("example")
* S wchar*字符串输出
* p 指针的地址
* g 自动选择格式化方式(输出最短)%f %e
*/
/* 特殊字符
* \n 换行
* \f 清屏换页
* \r 回车
* \t tab符
* \xhh ascii使用十六进制表示*/
2.5.2 格式化输入输出
// 格式化输入输出
//#include <cstdio> //格式化输入输出
#include <iostream>
using namespace std;
int main(){
// 格式化输入
int A, B;
scanf("%d\n%d",&A,&B);
printf("%d+%d=%d\n",A,B, (A+B));
// 格式化输出
char a = 'a';
long b = 20L;
float c = 3.14;
double d = 0.67891;
int e = 0xfff;
char f[] = "c++";
string g = "go";
printf("a=%c \n", a);
printf("b=%d \n", b);
printf("c=%.1f \n", c);
printf("d=%4.2f \n", d);
printf("e=%#x \n", e);
printf("f=%s \n", f);
printf("g=%s \n", g.c_str());
}
//out:
//1
//2
//1+2=3
//a=a
//b=20
//c=3.1
//d=0.68
//e=0xfff
//f=c++
//g=go
/* C++常用的几种格式化输入输出方式(函数)
*scanf(); 控制台键盘输入
*sscanf(); 字符串缓冲区输入与指定格式相符数据
*fscanf(); 文件输入
*printf(); 控制台输出
*sprintf();
*fprintf();
*/
2.6 运算符
2.6.1 算术运算符
#include <iostream>
using namespace std;
int main(){
// 算术运算符
/* +:加
* -:减
* *:乘
* /:取商
* %:取余
* ++:自增(不能用于表达式:eg:(x+y)++)
* --:自减
*/
int a = 11, c = 10, e = 5;
int b;
b = a++;
cout << "b[a++]:" << b <<endl;
cout << "a[a++]:" << a <<endl;
b = ++a; // 优先执行a+1
cout << "b[++a]:" <<b <<endl;
cout << "a[++a]:" <<b <<endl;
printf("%d(/)%d=%d \n", a, e, a/e);
printf("%d(%)%d=%d \n", a, e, a%e);
return 0;
}
//b[a++]:11
//a[a++]:12
//b[++a]:13
//a[++a]:13
//13(/)5=2
//13(%)5=3
2.6.2 关系运算符
#include <iostream>
using namespace std;
int main(){
//关系运算符
/* ==:等于
* !=:不等于
* >:大于
* <:小于
* >=:大于等于
* <=:小于等于
*/
int a , b;
a = 10;
b = 11;
if(a == b){
cout << "a==b" << endl;
} else{
cout << "a!=b" << endl;
}
return 0;
}
2.6.3逻辑运算符
#include <iostream>
using namespace std;
int main(){
//逻辑运算符
/* &&:and(python)
* ||: or(python)
* !: not(python)
*/
int a , b;
a = 1;
b = 0;
if (a&&b){
cout << "a&&b:true" << endl;
}
if(a||b){
cout << "a||b:true" << endl;
}
if(!(a&&b)){
cout <<"!(a&&b):true" << endl;
}
return 0;
}
2.6.4 位运算符
// 位运算:按照二进制格式进行运算(char short int long)
// 内存存储数据的基本单位是字节(byte) 一个字节=8位(bite);位是最小的数据量单位;
// 3(10) = 00000011(2)
#include <iostream>
using namespace std;
int main(){
//位运算符
/* &:位 "与"
* |:位 "或"
* ^: 位 "异或"
* ~: 位 "取反"
* <<: 左移
* >>: 右移
*/
int a , b;
a = 1;
b = 2;
cout << "a&b:" << (a&b) << endl; // a&b:0
cout << "a|b:" << (a|b) << endl; // a|b:3
cout << "a^b:" << (a^b) << endl; // a^b:3
cout << "b<<1:" << (b<<1) << endl; // b<<1:4
cout << "b>>1:" << (b>>1) << endl; // b>>1:1
cout << "~b:" << (~b) << endl; // ~b:-3
return 0;
}
2.6.5 其他运算符
#include <iostream>
using namespace std;
int main(){
// 指针运算符 *(取值) &(取址)
int par;
par = 10;
cout << "par:" << par << endl; // par:10
cout << "&par:" << &par << endl; // &par:0x61fe1c
cout << "*(&par):" << *(&par) << endl; // *(&par):10
//sizeof关键字 返回变量||数据类型的字节大小
cout << "char(size):" << sizeof(char) << endl; // char(size):1
cout << "int(size):" << sizeof(int) << endl; // int(size):4
cout << "float(size):" << sizeof(float) << endl; // float(size):4
}
2.6.6 运算符重载
#include <iostream>
#include <functional>
using namespace std;
//运算符重载
class money{
public:
int id;
int value;
money(){}
money(int id, int value):id(id),value(value){}
//运算符重载+函数(类内部)
int operator+(money m){
cout << "执行内部重载运算符+"<<endl;
return this->value + m.value;
}
//拷贝赋值运算符重载
void operator=(money &m){
cout << "执行赋值运算符重载"<< endl;
this->id = m.id;
this->value = m.value; // 默认是浅拷贝(若是存在指针成员 则使用深拷贝)
}
};
// 移动赋值运算符重载
class tt{
public:
int *p = nullptr;
tt(){}
tt(int *p):p(p){}
void operator=(const tt & t){
p = new int(*t.p); // 深拷贝
cout << "拷贝赋值运算符重载"<< endl;
}
void operator=(tt && t){
p = t.p;
t.p = nullptr;
cout <<"移动拷贝赋值运算符重载"<< endl;
}
//普通成员函数
void test(){
cout << "test...."<< endl;
}
//调用运算符重载
void operator()(int*p){
cout << "调用重载+携带数据:" << *p << endl;
}
~tt(){}
};
//运算符重载全局函数
int operator-(money m, money &m1){
cout << "执行外部重载运算符-"<<endl;
return m.value - m1.value;
}
//输出运算符重载
ostream& operator<<(ostream &out, money &m){//使用&out:cout不允许拷贝
out << "输出运算符重载:id=" << m.id << " value="<< m.value<< endl;
return out;
}
//输入运算符重载
void operator>>(istream &in, money & m){
in>>m.id >>m.value;
}
int main(){
// 算术运算符重载(简化代码 应用场景多样)
int a = 1, b = 2;
int c = a + b; // 通常的运算符应用
money m1(1,11);
money m2(2,22);
int a1 = m1 + m2; // 运算符重载应用(类内部)
int a2 = m1 - m2; // 运算符重载(全局)
cout << "c=" << c << " a1=" << a1 << " a2=" << a2 <<endl;
cout << "-------------1-----------"<< endl;
// 输出运算符重载
cout << m1 << m2<< endl;
cout << "------------2------------"<< endl;
// 输入运算符重载
money m3;
cout<<"输入:"<< endl;
cin>>m3;
cout<< "m3.id="<< m3.id<<" m3.value" << m3.value<<endl;
// 拷贝赋值运算符重载
cout << "------------3------------"<< endl;
cout << m1 << m2 << endl;
cout << "------------4------------"<< endl;
m1 = m2;
cout << m1 << m2 << endl;
// 移动赋值运算符重载
cout << "------------5------------"<< endl;
int* pt = new int(44);
tt t1(pt);
tt t2;
//t2 = t1;
t2 = move(t1);
cout << *t2.p << endl;
//cout << *t1.p << endl; // 编译错误
// 调用运算符重载
cout << "------------6------------"<< endl;
t2.test(); // 普通调用
t2(pt); // 重载调用
plus<int> p;
cout<< p(1, 2) << endl; // 重载调用应用
delete pt;
return 0;
}
/* 运算符重载实则是调用operator函数
* 输出运算符<<:cout对象禁止拷贝(父类的拷贝构造函数为私有) 它负责从缓冲区获取数据打印输出有且只有一个对象
* cout<<1<<2;链式输出需要返回值ostream out
* 不可使用运算符重载:
* 成员访问运算符 . || 域运算符 :: || 长度运算符 sizeof || 条件运算符 ?: || 预处理符号 #
*/
2.7 命名空间namespace
#include <iostream>
#include <string>
/*
//a.使用using声明 避免局部变量同名带来的编译过程中冲突(推荐)
using std::cin;
using std::cout;
using std::endl;
*/
using namespace std; //b.using 指示(不推荐)
int a = 100; //定义一个全局变量a
//1.通过namespace声明
namespace c_member {
int a = 20; // namespace 内部变量a
string name[]{"c", "c#", "c++"};
void fn(){
cout << "c_member::fn调用" << endl;
}
// 嵌套namespace
namespace c_web {
string name[]{"c_socket"};
void fn(){
cout << "c_member::c_web::fn调用"<< endl;
}
}
}
namespace web{
string name[]{"python_web", "go_web"};
void fn(){
cout << "web::fn调用" << endl;
}
}
//2.通过using namespace (指定函数 变量 类)使用(using namespace::name)
using namespace web;
//静态局部变量
void test_static_par(){
static int x = 10;
cout << "x1=" << x << "\t";
x += 1;
cout << "x2=" << x << endl;
}
int main(){
// 命名空间(全局):区分不同库中的变量 函数 类等
//1.
cout << "c_member------(namspace_name::方式)" << endl;
cout << "c_member::name>>" << c_member::name[0] <<endl; // c_member::name>>c
c_member::fn(); // c_member::fn调用
cout << "c_member::c_web------(namspace_name::方式)" << endl;
cout << "c_member::c_web::name>>" << c_member::c_web::name[0] <<endl; // c_member::c_web::name>>c_socket
c_member::c_web::fn(); // c_member::c_web::fn调用
cout << "分割线------------------------------------------" << endl;
//2.
cout << "web------(using namespace方式)" << endl;
cout << "web::name>>" << name[0] << endl; // web::name>>python_web
fn(); // web::fn调用
// 校验namespace内部变量作用域(全局变量&&局部变量)
cout << "分割线------------------------------------------" << endl;
int a = 10; //局部变量a
cout << "全局变量a>>" << ::a << endl; // 全局变量a>>100
cout << "c_member::a>>" << c_member::a << endl; // c_member::a>>20
cout << "局部变量a>>" << a << endl; // 局部变量a>>10
// 静态局部变量static type 变量名 (只会初始化一次)
test_static_par(); // x1=10 x2=11
test_static_par(); // x1=11 x2=12
return 0;
}
/*::范围解析运算符
*全局作用域符(::name) 作用域为全局命名空间(变量同名声明全局变量)
*类作用域符(class::name) 指定类型变量的作用域范围是具体的某个类
*命名空间作用域符(namespace::name) 指定类型变量的作用域范围是具体某个命名空间
*/
2.8 预处理器(宏)&&头文件
-----------par.h
//条件宏
#ifndef CODE_PAR_H // 定义宏(CODE_PAR_H 宏名称随意)
#define CODE_PAR_H // 执行宏
int abc = 111;
int check_min(int x, int y){
if(x>y){
return y;
} else
return x;
}
#endif //CODE_PAR_H // 结束
------------main.cpp
#include <iostream> # 头文件
#include "par.h"
using namespace std;
// 宏变量(大写)
#define AGE 24
#define A 2*2 // 不携带参数的宏a
#define B(q) q*q // 携带参数的宏b
#define C(q) (q)*(q)
#define D(x, y) x##y
#define F(x) #x
#define check_max(x,y)(x>y?x:y)
int main(){
/* 头文件使用
* 用于变量 函数 类的定义声明 在源文件.cpp中导入使用
* <>:默认在gcc编译器环境内查找; "":默认在当前工程查找 再到系统编译器环境查找
*/
cout << "par.h(abc)=" << abc << endl;
cout << "par.h(check_min函数)=" << check_min(2, 3) << endl;
printf("分割线----------------------->\n");
//预处理器(宏)
int g = A+A; // 2*2+2*2
int s = B(2+2); // 2+2*2+2=8
int k = C(2+2); // (2+2)*(2+2)=16
printf("g=%d s=%d k=%d\n", g, s, k); // g=8 s=8 k=16
printf("分割线----------------------->\n");
int y = D(11,22);
const char *z=F(abcdefg); // "abcdefg"
int max = check_max(33, 22);
printf("y=%d, z=%s, res=%d\n", y, z, max); // y=1122, z=abcdefg, res=33
printf("分割线---------预定义宏-------------->\n");
cout << "value__LINE__:" << __LINE__ << endl; // 行号493
cout << "value__FILE__:" << __FILE__ << endl; // 当前文件目录C:\Users\xyz\Desktop\code\main.cpp
cout << "value__DATE__:" << __DATE__ << endl; // 日期Oct 13 2020
cout << "value__TIME__:" << __TIME__ << endl; // 时间15:58:07
printf("分割线---------条件宏 控制代码执行-------------->\n");
#if AGE
cout << "if false:可实现注释效果\nif true 则执行语句 主要是判断变量宏AGE是否存在" << endl;
#else
cerr << "其他代码执行" << endl; // 针对栈满 cerr不缓冲 也可以直接输出信息(多用于错误输出)
#endif
}
/* 格式:#define 宏名称 表达式(数值等)
* 宏
* 1.宏变量:define AGE 10; 类似const 常量
* 2.条件宏主要用于判断是否存在某个宏变量
* 预处理器在编译前优先替换常量的值 通过宏可以方便修改常量的值
* 对于函数调用 使用宏定义简单功能的函数能提高程序的执行效率 不会在栈内存开辟空间 类似内联函数 不用再调用耗时耗资源
*/
2.9字符串
#include <iostream>
#include <string> // 提供c++string类 相关方法
#include <cstring> // 提供c string类 相关方法
using namespace std;
int main(){
// 1.c 字符串定义
char str[] = "good";
// char str[] = {'g', 'o', 'o', 'd', '\0'};
cout << "char str(size)=" << sizeof(str) << endl; //char str(size)=5
//操作(c)
char d[1];
strcpy(d, str); //拷贝字符串
cout << "d:" << d << endl;
strcat(d, "abc"); // 拼接字符串
cout << "d:" << d << endl;
strlen(d); // 字符串长度
cout << "d(length):" << d << endl;
// 2. c++(namespace string) 字符串定义
string a = "c++";
string b;
string c = " is no.1";
//操作(c++)
cout << "(b):" << (b=c) << endl; // 字符串赋值替换
cout << "(a+b):" << (a+=b) << endl; //字符串拼接
cout << "(a+b)(长度):" << (a+=b).length() << endl; // 字符串长度
cout << "c.find('i'):" <<c.find('i') <<endl; // 查找特定字符位置
cout << "b(添加元素):" << b.append("+abc") << endl;
cout << "b(特定位置插入元素):" << b.insert(8, "+efg") << endl;
cout << "b(特定元素位置):" << b.find("+efg") << endl;
cout << "b(修改元素):" << b.replace(8, 8, "") << endl; // pos:替换开始下标 n1:替换的范围 s:替换内容
cout << "(a+b)(截取特定位置元素):" << (a+b).substr(0, 3) << endl; //pos:开始下标 n:结束下标
}
/*
char str(size)=5
d:good
d:goodabc
d(length):goodabc
(b): is no.1
(a+b):c++ is no.1
(a+b)(长度):19
c.find('i'):1
b(添加元素): is no.1+abc
b(特定位置插入元素): is no.1+efg+abc
b(特定元素位置):8
b(修改元素): is no.1
(a+b)(截取特定位置元素):c++
*/
2.10 数组
// 数组定长的容器 使用连续性的一串地址存放数据 不允许添加删除 下标从0开始 可查询修改
// 数组定义:type(c++数据类型) arryName(数组名) [arry_size(数组元素数量必须大于0且为常量表达式)] (const int size = 10 ;>> int a[size];)
// 多维数组:type arryName [arry_size1][arry_size2]...(类比坐标系x,y,z)
#include <iostream>
using namespace std;
int main(){
//初始化int类型数组(2行3列)
//int a[2][3]{{1, 2, 3},{4, 5, 6}};
int a[2][3] = {
{1, 2, 3},
{4, 5, 6},
};
// int a[2][3] ={1,2,3,4,5,6}
// 修改数组内数据
a[0][0] = 99;
// 数组大小(长度) int size=sizeof(数组)/sizeof(int);
cout << "数组a大小:" << sizeof(a) / sizeof(a[0][0]) << "\n";
// 通过索引获取数组数据(从0开始)
for(int x=0;x<2;x++){
for (int y = 0; y < 3; y++) {
cout << "a[" <<x << "][" <<y <<"]>>>" << a[x][y] << endl;
}
}
// vector越界
vector<int> _arr{11, 22, 33, 44};
int arry[4] = {11, 22, 33, 44};
printf("_arr[4]:%d\n", _arr[4]);
printf("_arr.at(4):%d\n", _arr.at(4)); // vector .at可进行范围检测 判断是否越界再取数据
printf("arry[4]:%d\n", arry[4]); // 普通数组不存在检测机制 除非越界范围大 编译也会出错
return 0;
}
// 数组越界问题:会导致相邻地址的数组数据被越界数据覆盖
//vector(不定长数组容器应用)
#include <iostream>
#include <vector>
using namespace std;
int main(){
//vector(不定长数组容器)
//1.声明初始化(相似数组)
vector<int> arr{1,2,3}; // vector<int> arr={1,2,3};
// vector<int> _arr(4, 3); //非初始化 调用vector构造函数 相当于_arry长度为4 值均为3
//2.增删改查
cout<<"arr.push_back" << endl;
arr.push_back(4); // 添加(尾部添加)
for(int i=0;i<arr.size();++i){cout <<i <<"=" << arr[i] << endl;}
arr.insert(arr.begin()+1, 22); // 在数组下标1添加数据22
cout<<"arr.insert " << endl;
for(int i=0;i<arr.size();++i){cout <<i <<"=" << arr[i] << endl;}
cout << endl;
arr[3] = 5; // 修改数据
// arr.at(3) = 5; // 修改
cout<<"arr.at " << endl;
for(int i=0;i<arr.size();++i){cout <<i <<"=" << arr[i] << endl;}
cout << "查询遍历arr" << endl;
for(int i=0;i<arr.size();++i){ // 查询遍历
cout << i << "=" << arr[i] << endl;
}
arr.erase(arr.begin(), arr.begin()+2); // 删除下标0到1的数据[0,2)
cout<<"arr.erase " << endl;
for(int i=0;i<arr.size();++i){cout <<i <<"=" << arr[i] << endl;}
arr.clear(); // 清空数组数据
// vector越界
vector<int> _arr{11, 22, 33, 44};
int arry[4] = {11, 22, 33, 44};
printf("_arr[4]:%d\n", _arr[4]);
// printf("_arr.at(4):%d\n", _arr.at(4)); // vector .at可进行范围检测 判断是否越界抛出异常再取数据
printf("arry[4]:%d\n", arry[4]); // 普通数组不存在检测机制 除非越界范围大 编译也会出错
// 二维
//二维数组定义
// int a1[]{1, 2, 3, 4};
// int a2[]{11, 22, 33, 44};
// int a3[]{1, 2, 3, 4};
//
// int a[3][4]{
// {1, 2, 3, 4},
// {11, 22, 33, 44},
// {111, 222, 333, 444}
// };
//vector定义
vector< vector<int> > x{
{1, 2, 3, 4},
{11, 22, 33, 44},
{111, 222, 333, 444}
}; // 二维vector
for(vector<int> value:x){
for (int v:value){
cout << v << "\t";
}
cout << endl;
}
// 1 2 3 4
// 11 22 33 44
// 111 222 333 444
return 0;
}
/*
arr.push_back
0=1
1=2
2=3
3=4
arr.insert
0=1
1=22
2=2
3=3
4=4
arr.at
0=1
1=22
2=2
3=5
4=4
查询遍历arr
0=1
1=22
2=2
3=5
4=4
arr.erase
0=2
1=5
2=4
*/
2.11 struct结构体(c++ 类)
#include <iostream>
#include <string> // 集成了string类所有操作
using namespace std;
// 声明结构体类型_class
struct _class {
string cls_name;
string department;
int cls_id;
// 3.初始化结构体(构造函数)
_class (string a = "", string d = "", int c = 0){
cls_name = a;
department = d;
cls_id = c;
}
};
//声明结构体类型people->创建一个新的数据类型
struct people {
string name;
int age;
string gender;
string hobby;
_class class_info;
};
// 类
struct volume{
int x;
int y;
int z;
volume(){
cout << "无参构造"<< endl;
}
~volume(){
cout << "析构"<<endl;
}
};
int main(){
// 1.初始化结构体people(指定变量stu&&赋值)
people stu;
stu.name = "露娜";
stu.age = 20;
stu.gender = "女";
stu.hobby = "弹吉他";
stu.class_info.cls_name = "计算机应用技术1班";
stu.class_info.department = "计算机系";
stu.class_info.cls_id = 1;
// 2.初始化结构体people
// people stu = {"露娜", 20, "女", "play_guitar", "计算机应用技术1班", "计算机系", 1};
// 输出信息
cout << "name:" << stu.name << "\n";
cout << "age:" << stu.age << "\n";
cout << "gender:" << stu.gender << "\n";
cout << "hobby:" << stu.hobby << "\n";
cout << "cls_name:" << stu.class_info.cls_name << "\n";
cout << "department:" << stu.class_info.department << "\n";
cout << "cls_id:" << stu.class_info.cls_id << "\n";
// 3.堆内存创建结构体对象
people* s2 = new people;
(*s2).name = "zero";
(*s2).age = 25;
(*s2).gender = "男";
(*s2).hobby = "打球";
(*s2).class_info.cls_name = "计算机应用技术2班";
(*s2).class_info.department = "计算机系";
(*s2).class_info.cls_id = 3;
cout << (*s2).name << " " << (*s2).age << " " << endl;
// 4. 类
volume v; // 创建对象v
v.x = 1; // 访问对象成员
cout << v.x << endl;
return 0;
}
// struct 可以嵌套使用 可通过这个结构体类型(新数据类型)创建多个变量->类(多个实例)
// 不可在定义新的结构体内进行赋值操作(定义的过程单纯是创建新的数据类型)
// c++ struct结构体可以有析构函数和构造函数 默认类中成员变量为公有 但也可以私有 没有多态 继承机制
// 构造函数初始化结构体避免了 (指定变量 赋值初始化)这种方式因为某些成员(结构体内部的变量)未初始化 导致 后面成员不能初始化问题 同时也避免字符串编译问题
//struct与class区别:
//1.struct 成员默认public
//2.struct 多用于表示一个新结构类型
2.12 枚举类型
#include <iostream>
#include <string>
using namespace std;
enum struct GENDER{MALE, FEMALE};
class stu;
class stu{
public:
string name;
GENDER gender;
stu(string name, GENDER gender):name(name), gender(gender){}
};
int main(){
// 枚举类型
//1. 限定作用域
//1.1 定义
enum class COLOR{RED, GREEN=22, BLUE};
//1.2 取值
cout << (int)COLOR::RED <<endl; // 0
cout << (int)COLOR::GREEN <<endl; // 22
cout << (int)COLOR::BLUE <<endl; // 23
cout <<"-----------------------------"<<endl;
// 2.不限定作用域
enum LOGO{QQ, WEIXIN=222, MT};
// enum T{QQ, TAOBAO}; // 编译错误
cout << LOGO::QQ <<endl; // 0
cout << LOGO::WEIXIN <<endl; // 222
cout << LOGO::MT <<endl; // 223
// 3.应用
cout <<"-----------------------------"<<endl;
stu s("FSH", GENDER::MALE);
cout << "name=" << s.name <<endl;
cout << "gender=" << ((int)(s.gender)==0 ?"男": "女")<<endl;
}
/* 枚举类型
* 1.限定作用域
* 1.1 定义 enum class a{}; enum struct b {};
* 1.2 成员可以赋值 默认输出下标位置 有赋值操作会影响后边数据输出
* 2.不限定作用域
* 2.1 定义 enum xx{}
* 2.2 成员可以赋值 默认输出下标位置
*/
3. 条件判断
3.1 if
#include <iostream>
using namespace std;
int main(){
// 条件判断
cout << "输入数字:" << endl;
int a;
cin >> a;
if(a>=1 && a<=5){
cout << a << "\n 1<=a<=3";
}else if(a>5){
cout << a << "\n a>5";
}else{
cout << "a非整形|a非法输入";
}
}
/*if(条件一){}
* else if(条件二){}
* else{其他条件}
* 可以使用条件运算符 ?:
* exp1 ? exp2 : exp3; -> if(){}-else{}
* express1(true)->express2; || express1(false)->express3; eg:bool result = a > 3 ? 1: 0;
*/
3.2 switch -case
#include <iostream>
using namespace std;
int main(){
// 条件判断
cout << "是否检测到他|她说谎(0|1):" << endl;
char flag;
cin >> flag;
switch (flag) {
case '0':
cout << "说谎了";
break;
case '1':
cout << "没说谎";
break;
default:
cout << "观察中";
break;
}
}
/* 小结:
*case(常量且不能相同 否则switch无法判断执行的语句)
*break用于满足条件后结束switch语句
*default其他条件不满足则执行默认 可以省略break
*/
4. 循环
4.1 while
#include <iostream>
#include <windows.h> //c++:Sleep函数
using namespace std;
int main(){
int num = 3;
while(num <=3 && num >=0){
printf("倒计时:%d\n", num);
Sleep(1000); // ms
num --;
}
return 0;
}
// 使用continue && break 进行循环控制
4.2 do-while
#include <iostream>
#include <windows.h> //Sleep函数
using namespace std;
int main(){
int num = 3;
do{
printf("倒计时:%d\n", num);
Sleep(1000); // ms
num --;
}while(num <=3 && num >=0);
return 0;
}
// while && do-while :while先判断再执行 | do-while先执行后判断 确保循环至少执行了一次
4.3 for
#include <iostream>
using namespace std;
int main(){
//for 实现九九乘法表
int i, j;
for (i=1;i<10;i++){
for(j=1;j<=i;j++){
printf("%d*%d=%d\t", j, i, j*i);
}
printf("\n");
}
}
/*
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
*/
// c++11 for循环使用
// 1. for(i;i<10;i++) 类型
// 2.for(type i:容器(可序列化))
/* for(int val:{1,2,3,4}){cout << val;} {1,2,3,4} 任意序列*/
5. 函数
5.1 一般函数
#include <iostream>
using namespace std;
//内联函数
inline int fn(int x, int y){
return x + y;
//有返回值的自定义函数
/*格式:
* 函数return返回的类型 函数名(参数) {函数体执行代码(可省略)}
* return_type fn_name(par_list) {}*/
int min_v(int x , int y) {
if(x>y){
cout << "x-y(min):" << y;
}else if (x==y){
cout << "x-y(min):" << x || y;
}else{
cout << "x-y(min):" << x;
}
return 0;
}
// void 声明函数max_v不需要返回值 且参数名可忽略 参数类型必须存在
void max_v(int x, int y){
if(x>y){
cout << "x-y(max):" << x;
} else if(x==y){
cout << "x-y(max):" << y||x;
}else{
cout << "x-y(max):" << y;
}
}
// 指针形参对实参的影响
void change(int *a, int *b){
int t = *a;
*a = *b;
*b = t;
}
// 函数重载
int cal(int x, int y){
return x+y;
}
double cal(int x, double y){
return x+y;
}
int cal(int x, int y, int z){
return x+y+z;
}
int main(int arg, char** args){
//arg:参数的个数 && args:程序启动后携带的参数数据 .exe 后携带的参数(命令行启动)
cout << "arg=" << arg << endl;
cout << "args=" << args << " args[0]=" << args[0] << " args[1]=" << args[1] <<endl;
//调用函数
min_v(4, 5); // x-y(min):4
printf("\n");
max_v(2, 3); // x-y(max):3
int a=7, b=8;
printf("调用前(a-b):%d-%d\n", a, b); // 调用前(a-b):7-8
change(&a, &b);
printf("调用后(a-b):%d-%d\n", a, b); // 调用后(a-b):8-7
// 函数重载(函数名相同+参数个数&&类型&&顺序不同)
cout << "int-cal1:" << cal(1, 2) << endl; // int-cal1:3
cout << "double-cal2:" << cal(1, 2.4) << endl; // double-cal2:3.4
cout << "int-cal3:" << cal(1, 2, 3) << endl; // int-cal3:6
// 内联函数(不会有压栈的操作
// 直接将函数代码放置在调用位置
// 但是也会延长函数内部变量的生命周期)
int a=1, b=2;
cout << fn(a, b) << endl; // 3
return 0;
}
/*
形参:int x, int y 接收参数的变量;相当于局部变量 调用函数后会销毁;
实参: int a =2, b=3 传递给形参的变量;默认情况下形参不会直接对实参传递过来的值有影响;指针形参&&引用形参均可以对传递过来的实参变量
函数重载:函数名称相同 参数的类型&&个数&&顺序不同 用于实现类似功能的同名函数
函数参数传递值的方式:值传递(拷贝数据) && 引用传递(拷贝数据地址) && 指针传递(拷贝数据地址)
传递函数的参数是值传递 相当于值的拷贝 在函数内部默认不可修改 可使用指针(引用)参数进行修改
数组传递内存地址数据
*/
5.2 lambda函数(c++11)
#include <iostream>
using namespace std;
int _add(int a, int b){
return a+b;
}
int cal(int a, int b, int (*fn)(int, int )){
return fn(a, b);
}
int main(){
/*lambda 匿名函数
* [捕获外部变量供使用] (形参) mutable修饰符申明可以进行修改的捕获变量 异常设定-> 返回类型 {函数体}
* []()->int{}
* [capture](parms) mutable exception->return_type{body}
* 最简洁的表达式 [capture] {body}
* */
int a=1, b=2, c=3;
auto f = [](int d){printf("f-(d):%d\n", ++d);}; // (ps:auto自动匹配变量类型)
// auto f = [](int d){printf("f-(d):%d \n", ++d);return 0;}(a); // f-(a):2
auto f1 = [a] () mutable {printf("f1-(a):%d \n", ++a);}; // 捕获列表添加a 使用外部变量(mutable 申明可以对a修改)
auto f2 = [a] {printf("f2-(a):%d \n", a);}; // 捕获变量a且不能在函数体进行修改
auto f3 = [=, &a] {printf("f3-(a-b-c):%d-%d-%d \n", a=11, b, c);}; // a引用形式捕获 其他变量在函数体修改
auto f4 = [&, a] {printf("f4-(a-b-c):%d-%d-%d \n", a, b=22, c=33);};
// auto f5 = [this]{this->[]{printf("c++");};};
// 调用
f(a); // f-(d):2
f1(); // f1-(a):2
f2(); // f2-(a):1
f3(); // f3-(a-b-c):11-2-3
f4(); // f4-(a-b-c):1-22-33
//[](int d){printf("f-(d):%d\n", ++d);}(); // 直接调用
//void(*p)() = []{cout<<"111"<<endl;}; // 通过函数指针接收再调用
//p()
//lambda函数应用(参数传递)
int res1, res2;
res1 = cal(2, 3, _add);
res2 = cal(4, 5, [](int a, int b){return a*b;});
cout << "res1=" << res1 << "\t" << "res2=" << res2<<endl;
return 0;
}
/*[]:捕获列表其他用法(局部变量)
* [a,b,..]-捕获多个变量
* [=]值-拷贝所有外部变量值(不可再修改值)
* [&]引用-引用方式接收所有外部变量(可修改值)
*/
6. 对象
6.1 对象初识
#include <iostream>
#include <string>
using namespace std;
//类(class 关键字创建类)
class cls{
int flag; // 私有属性
public:
//属性(默认私有)
int cls_id;
string name;
//方法(默认私有)
void test(){
cout << "cls_id:" << cls_id << " name:" << name << endl;
}
// 外部 关联类成员函数
//1.类名::函数名
void test1(cls & c);
// 2.友元函数
friend void test2(cls & c);
// 3.友元类
friend class ss;
};
//友元类
class ss{
public:
void get_data(cls & c){
c.flag = 333;
c.test();
cout << "get_data调用"<< endl;
}
};
// 在类外部关联成员函数
void cls::test1(cls & c) {
c.flag = 111; //可访问修改类中成员
//c.test();
cout << "test1..." << endl;
}
// 通过友元函数关联
void test2(cls & c){
c.flag = 222; //可访问修改类中成员
//c.test();
cout << "test2..."<< endl;
}
int main(){
// 栈内存创建对象
cls c1;
// 堆内存创建对象
cls * c2 = new cls;
printf("------------------------------\n");
//访问对象成员(属性+函数方法)
c1.name = "c++1班";
c1.cls_id = 1;
c1.test();
c2->name = "c++2班";
c2->cls_id = 2;
c2->test();
//访问类中的成员函数
c2->test1(c1);
delete c2;
// 友元函数
printf("------------友元函数------------------\n");
test2(c1);
// 友元类
printf("------------友元类------------------\n");
ss s;
s.get_data(c1); // 通过友元类访问修改类成员
return 0;
}
/* 栈内存创建的对象 其方法保存在内存是一个指针 指向函数表 函数表通过栈内存函数地址调用函数
* 访问修饰符 public private protected
* public:类内部访问+类外部访问
* private:类中所有成员默认私有 只能在类内部或友元(friend 修饰符修饰的类后函数)可以访问使用 外部不可访问修改
* protected: 与私有类似 但是派生类内部也可以访问父类中成员
*
* 类中成员默认为私有 只能在类内部使用 通过public进行公有化声明 类外部也可使用
* 若采用分离式的编译 通常类在头文件.h进行声明 源文件.cpp实现成员函数
* 类中的成员函数默认都是内联函数 不会在栈内存开辟空间 直接放置再调用位置
* 在类外部访问修改类成员
* 1.通过在类外部创建全局函数并用类名::函数名声明
* 2.通过在类中声明friend 友元函数
* 3.通过在类中声明friend class 友元类
* 4.友元类(函数) 可以访问类中所有私有(保护)成员
*/
6.1.1 初始化列表形式 构造
#include <iostream>
using namespace std;
class A{
public:
A(int id){
cout << "A有参构造"<< endl;
}
};
class B: public A{
public:
B():A(1){
cout << "B有参构造"<< endl;
}
};
class C{
public:
const int c;
C(int c):c(c){}
};
class D{
public:
int & d; // 类成员为引用
D(int d):d(d){}
};
class E{
public:
A a; // 类成员为对象
E(int e):a(e){}
};
int main(){
// 初始化列表形式执行构造函数
//1.继承下 子类初始化 父类没有无参构造 手动调用父类有参
B b;
//2.const修饰的类成员
C c(1);
//3.初始化引用成员数据
D d(2);
//4.类中成员为对象 其对象没有无参构造 初始化对象
E e(3);
return 0;
}
6.2构造函数
6.2.1 构造函数初识
#include <iostream>
using namespace std;
class time{
public:
//属性
int hour;
int minute;
// 特殊函数(构造函数)
//1.普通无参构造函数
time(){
hour = 3; // 初始化属性
minute=50;
cout << "无参构造函数执行"<< endl;
}
//2.有参构造函数
time(int h){
hour = h;// 初始化属性
cout <<"有参1构造函数执行"<< endl;
}
//3.初始化参数列表构造函数
time(int hour, int minute):hour{hour}, minute{minute}{
cout << "有参2构造函数(初始化列表参数)执行"<< endl;
}
//普通函数
void test(){
cout<<"普通函数执行"<< endl;
}
};
int main(){
//栈内存创建对象
//1.无参构造函数
time t1;
time t2;
time t3(); // 不会创建对象 单纯函数的原型
//2.有参构造函数
time t4(5);
//堆内存创建对象
time* t5 = new time;
time* t6 = new time(5, 10);
//初始化对象属性
cout << "t1初始化:" << t1.hour << "-" << t1.minute << endl;
cout << "t4初始化:" << t4.hour << "-" << t4.minute << endl;
cout << "t6初始化:" << t6->hour << "-" << t6->minute << endl;
delete t5;
delete t6;
return 0;
}
/*
1.构造函数完成对象成员属性赋值操作
2.默认创建对象会执行类的无参构造函数(即使类中没有) 也可自定义构造函数
3.无参构造可以添加关键字 time()=default 使用默认机制实例无参对象
4.构造函数具备重载的特性(无参构造与有参构造 对象创建自动重载调用构造函数)
5.初始化列表参数构造函数必须使用情况(执行构造函数前完成初始化赋值):有继承关系 成员中有常量或者引用
6.存在有参构造函数后 不能再执行进行无参构造函数的调用 对象需要初始化携带参数
7.静态成员属性static int id 需要在类外部进行初始化
8.有一个参数的构造函数 可以在实例对象时触发隐式调用->time t4 = 5;会触发有参构造函数;可以使用 explicit 关键字限制隐式调用
*/
6.2.2 委托构造函数
//初始化列表参数形式执行构造函数(委托构造函数)
#include <iostream>
using namespace std;
class time{
public:
int hour;
int minute;
//1.普通无参构造函数(初始化列表参数形式)
time():time(5, 1){
cout << "无参构造函数执行"<< endl;
}
//2.有参构造函数(初始化列表参数形式)
time(int h):time(h, 3){
cout <<"有参1构造函数执行"<< endl;
}
//3.初始化参数列表构造函数
time(int hour, int minute):hour{hour}, minute{minute}{
cout << "有参2构造函数(初始化列表参数)执行"<< endl;
}
};
int main(){
//通过初始化列表参数形式执行构造函数创建对象
time t1;
return 0;
}
// 在参数少的构造函数委托给参数多的构造函数
// 委托构造函数:创建对象 根据参数多少 自选调用的构造函数 time(){}&&time(int){} 委托给time(int ,int){} 使用初始化列表参数方式进行
6.2.3 拷贝构造函数
//已有对象拷贝执行拷贝构造函数
#include <iostream>
using namespace std;
class cls{
public:
int cls_id;
cls(int cls_id):cls_id(cls_id){
cout << "有参构造函数执行"<< endl;
}
//拷贝构造函数
cls(const cls &c){
// 已有对象数据拷贝到另一个对象
this->cls_id = c.cls_id; // cls_id=c.cls_id
cout << "拷贝构造函数执行" << endl;
//c.cls_id = 3; // const约束-报错
}
~cls(){
cout << "析构函数执行-" <<cls_id<< endl;
}
};
int main(){
//拷贝构造
cls c1(2); // 初始化对象
cls c2 = c1; // 拷贝对象
cout << "*pc1=" << &c1 << " c1.cls_id=" << c1.cls_id << endl;
cout << "*pc2=" << &c2 << " c2.cls_id=" << c2.cls_id <<endl;
return 0;
}
/*
* 拷贝构造(已有一个对象 从已有对象进行拷贝)
* const 禁止修改原对象初始化成员数据
* 拷贝构造函数参数使用&引用接收 避免再次拷贝 形成递归拷贝问题
* 禁止触发拷贝构造函数:拷贝构造函数变为私有private或者拷贝构造函数cls(const cls & c)=delete{}
*/
6.2.4 移动拷贝函数
#include <iostream>
using namespace std;
class subject {
public:
int* id= nullptr;
subject(){
cout <<"无参构造"<<endl;
}
subject(int* id):id(id){
cout <<"有参构造"<<endl;
}
subject(const subject &s){
id = new int(*s.id); // 类成员有指针 使用深拷贝
cout <<"拷贝构造"<<endl;
}
// 移动构造(原有对象数据移动到新对象数据)
// 默认编译器不提供移动构造函数(反而执行拷贝构造)
subject(subject && s){
id = s.id; // 新对象指向原有对象成员数据地址
s.id = nullptr; //原有对象指针成员 不在指向之前数据的地址
cout << "移动构造"<< endl;
}
~subject(){
cout <<"析构"<< endl;
}
};
int main(){
// 拷贝
int* i = new int(1);
subject s(i);
subject s1 = s; //深拷贝执行拷贝构造函数
cout << s.id << "\t"<<s1.id << endl; // 堆内存数据地址 0x10a6e20 0x10a6e40
cout <<"------------------------"<< endl;
// 移动构造(接收右值)
int* i1 = new int(2);
subject s2(i1);
subject s3 = move(s2); // move(转换成右值)
cout << s2.id <<"\t"<< s3.id <<endl; // 0 0x1e6e60(执行移动构造函数 且s2指针成员执行的数据地址不再存在 移动给了s3)
cout <<"------------------------"<< endl;
return 0;
}
/*
移动构造
使用场景:对象有指针成员 且原对象不再使用 新对象与原对象一样
比较:相较于拷贝构造 节省了开辟新空间 和拷贝的操作
实质:是类成员数据权限的交换 把原对象的指针成员指向的数据地址 变为新对象所拥有 原对象指向为空
*/
6.3 析构函数
#include <iostream>
using namespace std;
class cls{
public:
cls(){
cout << "构造函数执行" << endl;
}
~cls(){
cout << "析构函数执行" << endl;
}
};
void create_object(){
//栈内存形式创建对象
//函数调用结束则销毁对象c
cls c;
}
int main(){
// 栈内存形式创建对象
cout << "---------1-----------"<< endl;
create_object();
cout << "---------2-----------"<< endl;
// 堆内存形式创建对象
cls* c1 = new cls;
cout << "---------3-----------"<< endl;
delete c1;
return 0;
}
/* 析构函数没有参数 没有返回值 函数名是类名 是~(无参构造函数) 取反
* 对象销毁时自动调用
*/
6.4 继承
6.4.1 继承初识
#include <iostream>
using namespace std;
// 父类parent
class parent{
public:
int id=1;
void get(){
cout << "money=" << money <<endl;
cout << "mid=" << mid <<endl;
}
private:
int money = 2;
protected:
int mid = 3;
};
// son继承parent
class son: public parent{
};
int main(){
//继承
parent p;
son s;
p.get();
s.get();
cout << p.id << endl;
cout << s.id << endl;
}
// 单继承格式:class 子类: 访问修饰符(public private protected) 父类
// 多继承格式: class 子类: 访问修饰符 父类1, 访问修饰符 父类2,.....
6.4.2 修饰符修饰的继承
#include <iostream>
using namespace std;
// 父类parent
class parent{
public:
int id=1;
void get(){
cout << "id=" << id <<endl; // 父类公有
cout << "money=" << money <<endl; // 父类私有
cout << "mid=" << mid <<endl; // 父类保护
}
private:
int money = 2;
protected:
int mid = 3;
};
// son继承parent
class son1: public parent{
void get1(){
cout << "====son1=====" << endl;
cout << "id=" << id << endl;
//cout << "money=" << money << endl; // 父类是私有 子类也是 不可访问
cout << "mid=" << mid << endl; // 父类为protected 子类内部可以访问
}
};
class son2: private parent{
void get2(){
cout << "====son2=====" << endl;
cout << "id=" << id << endl; // 子类为私有
//cout << "money=" << money << endl; // 子类为私有 类内部不可访问
cout << "mid=" << mid << endl; // 子类为私有 子类内部可以访问
}
};
class son3: protected parent{
void get3(){
cout << "====son3=====" << endl;
cout << "id=" << id << endl;
//cout << "money=" << money << endl; // 父类是私有 子类也是 不可访问
cout << "mid=" << mid << endl; // 父类为protected 子类内部可以访问
}
};
int main(){
// public继承
son1 s1;
s1.id; // 父类公有 子类公有
//s1.money; // 父类私有 子类私有 (类内外部不可访问)
//s1.mid; // 父类保护 子类保护 (类外部不可访问, 内部可以访问)
cout << "---------------------------------"<< endl;
// private继承
son2 s2;
//s2.id; // 父类公有 子类私有
//s2.money; // 父类私有 子类私有
//s2.mid; // 父类保护 子类私有
// protected继承
son3 s3;
//s3.id; // 父类公有 子类保护(在子类内部可访问)
//s3.money; // 父类私有 子类私有
//s3.mid; // 父类保护 子类保护
}
/* public继承:子类成员与父类成员权限一致
* private继承: 子类成员均变为私有权限
* protected继承:父类成员的公有和保护成员变为保护权限 私有不变
* 子类继承了父类所有函数方法 除了
* 1.父类构造 析构 拷贝构造函数
* 2.父类重载运算符函数
* 3.父类的友元函数
*/
6.4.3 继承下构造 析构
#include <iostream>
using namespace std;
class parent {
public:
parent(){
cout << "parent构造" <<endl;
}
~parent(){
cout << "parent析构" <<endl;
}
};
class son1: public parent{
public:
// 默认的无参构造
// 默认先调用父类构造parent() 再调用子类构造
son1():parent(){
cout << "son1构造"<< endl;
}
~son1(){
cout << "son1析构"<< endl;
}
};
// 继承下手动调用有参构造
class parent2{
public:
parent2(int id){
cout <<"parent2有参构造" << id << endl;
}
~parent2(){
cout <<"parent2析构" << endl;
}
};
class son2: public parent2{
public:
//父类没有无参构造情况
//子类继承父类 子类有参构造执行需要优先调用父类有参构造parent2(xx)
//使用初始化列表方式
son2(int id):parent2(id){
cout << "son2有参构造"<< id <<endl;
}
~son2(){
cout << "son2析构"<<endl;
}
};
int main(){
// 继承下构造函数
// 1.无参
son1 s1; // parent构造 son1构造
cout << "------------------------" << endl;
// 2.有参
son2 s2(10); // parent2有参构造10 son2有参构造10
cout << "------------------------" << endl;
// 继承下析构函数
//son2析构
//parent2析构
//son1析构
//parent析构
}
/* 继承下构造函数
* 1.优先调用父类构造 再执行子类构造
* 2.有参构造 默认先调用父类无参构造 再调用子类构造
* 3. 父类没有无参构造 子类实例化对象带参数 调用有参构造 实质是初始化列表方式调用父类有参构造son1():parent(){}
* 继承下析构函数
* 1.优先调用子类析构 再调用父类析构
* 2.调用顺序与构造函数调用相反
*/
6.4.4 多继承
#include <iostream>
using namespace std;
//-类原型声明
class A;
class B;
class A{
public:
B &b; // 引用或指针形式
A(B& b):b(b){}
};
class B{};
class F{
public:
F(){
cout <<"F构造"<< endl;
}
~F(){
cout <<"F析构"<< endl;
}
};
class M{
public:
M(){
cout <<"M构造"<< endl;
}
~M(){
cout <<"M析构"<< endl;
}
};
//继承F M
class S: public F, public M{
public:
S(){
cout <<"S构造"<<endl;
}
~S(){
cout << "S析构"<<endl;
}
};
int main(){
// 多继承
S s;
// 类前置必要性
B b;
A a(b);
}
/* 多继承调用顺序取决于继承顺序
* 类前置声明-类原型
* 1.类内部实例化其他类
* 2.使用引用或指针形式传递
*/
6.5 多态
6.5.1 虚函数
#include <iostream>
using namespace std;
//虚函数
class A{
public:
// virtual A(){} // 构造虚函数编译错误
A(){
cout << "A构造"<<endl;
}
virtual ~A(){
cout << "A析构"<<endl;
}
virtual void test(){
cout << "A-test..."<<endl;
}
};
class B: public A{
public:
B(){
cout << "B构造"<<endl;
}
~B(){
cout << "B析构"<<endl;
}
void test(){
cout <<"B-test..."<< endl;
}
};
//纯虚函数
class C{
public:
virtual void test1() = 0; // 只用作子类重写函数的声明 定义新内容在子类实现
};
class D: public C{
public:
void test1(){
cout << "D执行111"<<endl;
}
};
class E: public C{
public:
void test1(){
cout << "E执行222"<<endl;
}
};
int main(){
// 虚函数
A *pa = new B; // 父类添加虚函数
delete pa;
cout<<"---------------------"<< endl;
B b;
A & a = b;
cout<<"---------------------"<< endl;
// 纯虚函数
// C c1; // 禁止纯虚函数创建实例
C * pc = new D();
pc->test1(); // D执行111
delete pc;
cout<<"---------------------"<< endl;
C* pcc = new E();
pcc->test1(); // E执行222
delete pcc;
return 0;
}
/* 虚函数
* 1. 实现多态的机制
* 2. 实现父类指针(引用) 指向子类实例的前提下 通过父类调用子类的成员函数
* 3. 使用virtual关键字声明
* 4. 虚函数=0 为纯虚函数
* 5. 对象成员有虚函数 实则是编译器在处理虚函数时
* 往对象添加一个隐藏的指针 指针指向一个数组 数组内部存放虚函数的地址
* 6. 构造函数不能为虚函数(有对象才有虚函数)
* 7. 析构函数可以为虚函数
* 7.1 继承下父类指针接收子类对象 必须在父类析构加上虚函数 不加不会执行子类析构
* 纯虚函数
* 1.纯虚函数没有函数体 声明时 函数名后=0
* 2.纯虚函数在父类只作为对子类重写函数的声明 实现均在子类实现
* 3.有纯虚函数的类为抽象类 禁止其创建对象 且继承父类的子类必须实现纯虚函数的实现 否则子类也无法创建实例 但是父类可以实现其他方法函数 继承下 子类亦可调用
*/
6.5.2 多态实例
#include <iostream>
using namespace std;
class animal{
public:
virtual void run(){
cout <<"animal-run..."<<endl;
}
// void run(){
// cout <<"animal-run..."<<endl;
// }
};
class cat: public animal{
public:
void run(){
cout << "cat-run..." << endl;
}
};
class dog: public animal{
public:
void run(){
cout << "dog-run..."<< endl;
}
};
class Home_Pet{
public:
void pet_active(animal * a){
a->run();
}
};
int main(){
//多态
//1.静态多态(静态联编)
cout<<"------------1------------"<< endl;
animal a;
cat c;
a.run();
c.run();
cout<<"------------2------------"<< endl;
animal *a1 = new animal();
cat *c1 = new cat();
animal * a2 = new cat(); //静态联编:animal-run...
a1->run();
a2->run();
c1->run();
cout<<"------------3------------"<< endl;
//2.动态多态(动态联编)
animal * a3 = new cat(); // 动态联编:父类成员函数为虚函数:cat-run...
a3->run();
//3.多态应用
cout<<"------------4------------"<< endl;
Home_Pet h;
dog *d4 = new dog;
cat *c4 = new cat;
h.pet_active(d4); // dog-run...
h.pet_active(c4); // cat-run...
delete a1, a2, a3, c1, d4, c4;
return 0;
}
/* 多态
* 1.继承
* 2.子类重写父类同名函数
* 联编机制
* 1.静态:编译过程决定调用的对象函数(函数)(默认)
* 2.动态:运行过程决定调用的对象函数(1.使用虚函数 virtual关键字修饰的成员函数)
* 动态类型:运行期间确定的数据类型
* 静态类型:编译期间确定的数据类型
* 虚函数
* 1.virtual 修饰的函数为虚函数
* 2. 虚函数=0 为纯虚函数
*/
7. 指针
7.1 一级指针 二级指针
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main(){
//指针(指针声明type* 变量名)
int a[5]={10,20,30,40}, *pa;
pa = a; // 或者pa = &a[0]; 只是a指向数组的首个元素地址 &a[0]指向数组第一个元素地址
cout << pa << " " << a << endl; // 0x61fde0 0x61fde0
cout << "a+1 " << a+1<< endl; // a+1 0x61fde4
cout << "&a[0] " << &a[0] << endl; // &a[0] 0x61fde0
cout << "&a+1 " << &a+1 << endl; // &a+1 0x61fdf4
cout << "a+5 " << a+5 << endl; // a+5 0x61fdf4
printf("分隔符------------------------------------------------------>\n");
// 指针类型(右值&(连字号运算符)获取内存地址)
int b= 1, *pb; // *pb 类型:int* (剔除指针名)
pb = &b;
cout << pb << " "<< *pb << endl; // 0x61fe04 1(取址取值)
cout << &b << " " << b << endl; // 0x61fe04 1
printf("分隔符------------------------------------------------------>\n");
// 指针所指向的类型
int *(*pc)[] = NULL; // 指针pc所指向的类型 int*()[](剔除指针名&&左*)
//指针的值(*获取指针的值->解引用的过程)
int *pd = NULL; // 空指针pd
cout << "空指针pd的值:" << pd << endl; // pd的值:0
printf("分隔符------------------------------------------------------>\n");
// 二级指针(**pg)
int g=10, *pg, **ppg;
pg = &g, ppg = &pg;
cout << g <<" "<< &g << endl;
cout << *pg <<" " << pg << endl;
cout << **ppg << " " << *ppg << " " << ppg << endl;
// 10 0x61fdd8
// 10 0x61fdd8
// 10 0x61fdd8 0x61fdd0
//常量与指针
printf("------------------------\n");
int n = 111, n1 = 222;
const int *pn = &n1; // 指针指向一个常量
pn = &n1; // 可以进行重新指向
//*pn = 112; // 不可进行解引用修改数据
printf("------------------------\n");
//常量指针
int* const pp = &n; // 初始化一个常量指针
*pp = 333; // 解引用可以进行数据修改
//pp = &n1; // 常量指针不可进行重新指向
//指针常量指向常量
const int* const ppp = &n;
//*ppp = 444;
//ppp = &n1; // 不可进行重新指向 亦不可进行解引用数据修改
return 0;
}
/*运算符优先级: ()>[]>*
* 指针 也是一个变量 也占用内存 明确指针的内存地址是其本身开辟空间内存的地址 和指针的值是指向的变量的内存地址 区别
* 空指针 可以使用int* p = 0 || int* p = nullptr(c++11) || int* p = NULL声明
* 指针指向常量 不可进行解引用修改地址所指向的数据 可以进行重新指向新的地址
* 常量指针 不可进行重新指向 可以解引用进行数据修改
* 二级指针(指针的指针)存放一级指针在内存中的地址 ;一级指针存放变量数据的内存地址;
*/
7.2 指针与函数
#include <iostream>
#include <synchapi.h>
using namespace std;
// 指针函数(函数返回指针)
int* change(){
int n = 100;
// 返回栈内存的一个地址 函数执行结束可能存在局部变量被销毁释放
//return &n;
// 解决:使用堆内存进行开辟新内存空间解决局部变量被销毁问题
return new int(n);
};
//函数指针
void who(string name){
cout<< name << endl;
}
void who_say(string x, void(*p)(string)){
p(x);
}
int main(){
// 指针函数的问题(局部变量值)
int* data = change();
Sleep(1000); // 延时操作
cout << "data=" << *data << endl; // data=0(return &n结果)
printf("-------------------------\n");
cout << "data=" << *data << endl; // data=100(return new int(n)结果)
delete data; // 释放堆内存data
data = nullptr; // 避免指针随机指向内存 占用资源
printf("-------------------------------------------\n");
//函数指针
who("mary"); // 直接调用函数
void (*pf)(string) = who; // 没有返回值 有参数的函数指针
pf("peter"); // 函数指针调用
printf("-------------------------------------------\n");
//函数指针应用
who_say("lory", pf); // lory
return 0;
}
/* 函数指针与指针函数的区别
* 函数指针:是指针 指向函数; 指针函数:是函数 返回值是指针;
* 函数指针: type (*fn)(int); 指针函数: type *fn(int);
* 函数指针应用:降低耦合度 直接使用函数指针传递 调用函数进行使用
*/
7.3 指针与数组
#include <iostream>
using namespace std;
int main(){
int d[]{1,2,3,4}, *pd = NULL; // 空指针pd
cout << "空指针pd的值:" << pd << endl; // pd的值:0
cout << "d输出的值:" << d << " sizeof(d):" << sizeof(d) << endl;
//指针与数组
pd = d; // ->pd = &d[0]
cout << "pd的值:" << *pd << " sizeof(*pd):" << sizeof(pd)<<endl; // pd的值:1 sizeof(*pd):8 (*指针在表达式中表示获取指针值)
cout << "通过指针pd访问数组元素(推荐使用:确保访问的一定是数组元素):" << endl; // *(d+1)=2 ->d[1]=2
while (pd<=&d[sizeof(*d)-1]){
cout << pd << " " << *pd << endl;
pd ++;
}
/*
通过指针pd访问数组元素:
0x61fdc0 1
0x61fdc4 2
0x61fdc8 3
0x61fdcc 4
*/
printf("数组指针-----------------用法和其他指针相同------------------------------------->\n");
int q[3] = {1,2,3};
int (*pq)[3] = &q;
cout << pq << " " << *pq << " " << &q[0]<<endl; // 0x61fdf4 0x61fdf4 0x61fdf4
cout << pq+1 << " " << *(pq+1) << " " << *(pq+2) << endl; // 0x61fe00 0x61fe00 0x61fe0c
cout << **pq << " " << *pq[0] << endl; // 1 1 (右值*取值解引用)
// *pq指向数组首个元素地址 == *(pq+1)是下标为1数组的元素地址 == pq指向整个数组的首地址;
// *(pq+1) 实则是地址的偏移 偏移了指针指向类型(int=4[64位系统])字节
// *(*pq):解引用首个元素地址指向的值;*(pq[0]):*(数组首个元素地址)=地址指向的首个元素值
printf("指针数组--------------------------------------------------------------------->\n");
int t[4]={1,2,3,4};
int* s[4]; // 定义一个指针数组
s[0] = &t[0]; // 赋值操作
cout << *s[0] << endl; // 1
return 0;
}
/* 优先级:()>[]>*
* 指针数组与数组指针区别
* 指针数组:是数组 所有元素均为指针类型; 数组指针:是指针 指向一个数组的首元素地址;
* 指针指向数组默认指向数组首元素地址;可以通过偏移*(arry+1) 获取所有数组数据
*/
7.4 指针与结构体
#include <iostream>
#include <vector>
using namespace std;
//指针与结构体
struct B{
int x;
int y;
vector<int> z;
};
int main(){
// 指针与结构体
B bb={1,2}; // 初始化结构体 bb
(bb.z).push_back({11});
B* pbb = &bb; //声明一个指向bb的 B*类型指针
// 通过指针访问结构体元素
cout << pbb->x << " " << (bb).x<< endl;
cout << pbb->y << " " << bb.y << endl;
cout << pbb->z.at(0) << " " << bb.z.at(0) << endl;
printf("--------------------------------------\n");
//通过偏移位置(指针变量+变量类型长度)获取结构体数据
int *pbbb = (int*)&bb; // 声明一个指向bb的int*类型指针
cout << *pbbb << endl;
cout << *(pbbb+1) << endl;
return 0;
}
7.5 智能指针
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class C;
class D;
class E;
class A{
public:
int id;
A(int id):id(id){cout<<"构造"<< id <<endl;}
~A(){cout<<"析构"<< id << endl;}
};
// shared_ptr循环拷贝问题
class B{
public:
int id;
shared_ptr<C> sp_c;
void get_c(shared_ptr<C> sp_c){
this->sp_c = sp_c;
}
B(int id):id(id){cout<<"B构造"<< id <<endl;}
~B(){cout<<"B析构"<<id <<endl;}
};
class C{
public:
int id;
shared_ptr<B> sp_b;
void get_b(shared_ptr<B> sp_b){
this->sp_b = sp_b;
}
C(int id):id(id){cout<<"C构造"<<id << endl;}
~C(){cout<<"C析构"<<id << endl;}
};
// weak_ptr解决循环拷贝问题
class D{
public:
int id;
//shared_ptr<E> sp_e;
weak_ptr<E> wp_e;
void get_e(shared_ptr<E> sp_e){
this->wp_e = sp_e;
}
D(int id):id(id){cout<<"D构造"<< id <<endl;}
~D(){cout<<"D析构"<<id <<endl;}
};
class E{
public:
int id;
//shared_ptr<D> sp_d;
weak_ptr<D>wp_s;
void get_d(shared_ptr<D> sp_d){
this->wp_s = sp_d;
}
E(int id):id(id){cout<<"E构造"<<id << endl;}
~E(){cout<<"E析构"<<id << endl;}
};
int main(){
//智能指针
//1.unique_ptr
A * pa = new A(1); // 构造1
cout << "-----------------1----------------"<<endl;
// 1.1初始化
unique_ptr<A> p2(new A(2)); // 构造2 析构2 :自动释放指针对象
// unique_ptr<A> p3 = p2; // 编译错误 :禁止拷贝
int* pi = new int(1);
unique_ptr<int> p4(pi);
// unique_ptr<int> p5(pi); // 编译错误:禁止两个unique_ptr包装同一个指针
// 1.2 解引用取值
cout << "p4取值:" << *p4 <<endl;
cout << "p4取值:" << *(p4.get()) <<endl;
// 1.3 手动释放
p4.reset();
p2.reset(pa); // 析构1: 释放pa
cout << "-----------------2----------------"<<endl;
//2.shared_ptr
//2.1 初始化
shared_ptr<A> sp(new A(3));
cout <<"计数:" << sp.use_count() <<endl;
shared_ptr<A> sp1 = sp; // 可以进行拷贝
cout <<"计数:" << sp.use_count() <<endl;
//2.2 通过计数为0释放
sp.reset();
cout <<"释放计数:" << sp.use_count() <<endl;
sp1.reset();
cout <<"释放计数:" << sp.use_count() <<endl;
//2.3 循环拷贝问题
cout << "-----------------3----------------"<<endl;
B * pb = new B(111);
C * pc = new C(222);
// 智能指针shared_ptr
shared_ptr<B> sp_b(pb);
shared_ptr<C> sp_c(pc);
// 产生shared_ptr循环拷贝 未析构
pb->get_c(sp_c); // B构造111
pc->get_b(sp_b); // C构造222
//3.weak_ptr
//3.1 初始化(多用于shared_ptr)
cout << "-----------------4----------------"<<endl;
int* pii = new int(4);
shared_ptr<int> sp2(pii);
cout << "计数:" << sp2.use_count() << endl;
shared_ptr<int> sp3 = sp2;
cout << "计数:" << sp3.use_count() << endl;
weak_ptr<int> wp(sp2);
cout << "计数:" << wp.use_count() << endl; // 计数不会增加
//3.2 取值
cout << "wp取值:"<< *wp.lock()<<endl;
cout << "wp取值:"<< *(wp.lock().get())<<endl;
//3.3 解决shared_ptr循环引用问题
cout << "-----------------4----------------"<<endl;
D * pd = new D(333);
E * pe = new E(444);
// 智能指针shared_ptr
shared_ptr<D> sp_d(pd);
shared_ptr<E> sp_e(pe);
// shared_ptr不会循环拷贝 构造+析构
pd->get_e(sp_e);
pe->get_d(sp_d);
}
/* 智能指针必要性(堆内存数据) 避免出现下面问题:
* 1.野指针: 声明指针未初始化 int *p; 解决:int *p = nullptr;
* 2.重复释放:对同一指针多次delete操作; 解决:delete 一次
* 3.内存泄露:int *p = new int(4); 未对delete p;操作 堆内存未回收; 解决:delete p;
*
* 智能指针c++11
* 1.需要加载头文件<memory>
* 2.三种:unique_ptr(唯一) shared_ptr weak_ptr
* 3.实质均是类对象
* 4.多为栈内存智能指针 对堆内存数据释放 堆内存智能指针没有意义
*
* unique_ptr
* 1.唯一指针 初始化unique_ptr<类型> p(指针)
* 2.不可进行拷贝操作 同一个指针不可同时用unique_prt包装
* 3.默认自动释放 可以进行显式(手动)释放内存 p.reset();
*
* shared_ptr
* 1.共享(同一内存)指针 初始化shared_prt<类型>p(指针);
* 2.可进行拷贝操作 但是作为类成员 可能产生循环拷贝问题
* 3.通过p.use_cout()判断计数 计数为0则释放
*
* weak_ptr
* 1. 弱指针 初始化weak_ptr<类型> p(shared_ptr or weak_ptr)
* 2. 指针包装多用于shared_ptr 可解决shared_ptr循环拷贝问题
*/
8.引用
#include <iostream>
using namespace std;
int t1(int a, int b){return 0;}
int t2(int &a, int &b){return 0;}
int t3(int &&a, int &b){return 0;}
int t4(int &&a, int &&b){return 0;}
//返回值为引用
int& t5(int &a){
return a;
}
int t6(int &b){
return b;
}
int main(){
//左值引用(左值引用默认只接收左值 使用const关键字可以使用右值)
int a = 1, b = 2;
int& c = a; // c:左值引用; a:左值
// int &e = 11; // 报错
const int &d = 11;
printf("--------------------------\n");
//右值引用(c++11使用 只接收右值 不能接收左值)
//int && g = a; // 报错
int && f = 3; // f:右值引用; 2:右值
printf("-----------函数调用---------------\n");
t1(a, b);
t2(a, b);
t3(1, b);
t4(1, 2);
t5(a) = 111;
//t6(a) = 111; // 报错
cout << (t5(a) = 111) << endl; // 111
// 函数的返回值为引用 则编译器不会为返回值创建临时变量 直接返回变量的引用 若返回局部变量 则会在函数调用结束后销毁
}
/* 左右值判断: 左值||右值 = 右值 或 是否能获取地址 能则是左值 不能则是右值
* 引用:变量别名 不占用内存 指向目标变量的内存地址 不会开辟新空间;
* 不存在空引用 定义时必须初始化指向一个变量;数组没有引用 数组是一堆数据 引用只能指向一个数据;
* 变量可以有多个引用 引用只能指向一个变量;
* 应用:变量起别名&&形参接收参数(非拷贝 直接指向传递过来的实参变量值 效率高)&&引用作为返回值不会在内存中产生副本;
*/
9. 异常处理
#include <iostream>
#include <vector>
using namespace std;
int div1(int a, int b){
if(b==0){
//1.常规异常处理
//exit(111); //退出程序操作
//abort(); // 终止程序
//2.抛出异常(exception)
throw runtime_error("b不能为0");
}
int res = a/b;
return res;
}
int main(){
// 异常(exception)捕获
try{
div1(1, 0);
} catch (runtime_error e) {
cout << e.what() <<endl;
}
cout << "-------------------------------"<< endl;
vector<int> v = vector<int>{1,2,3};
try{
v.at(100);
throw out_of_range("越界");
}catch (...) {
cout << "任何类型异常捕获信息" << endl;
}
return 0;
}
/* 异常
* 所有异常均继承自exception
* 1.异常处理
* 1.1 常规异常处理 exit(错误码); || abort();
* 1.2.抛出异常 throw 异常子类(错误信息) exception(异常父类)
* 2.异常捕获
* 2.1 常规:try{可能存在异常代码}catch(异常类型 exception){异常处理}
* 2.2 多个catch 也只会执行一个
* 函数异常声明 void fn(int x) throw(int,char*,);
*/
10. 动态内存
10.1 new delete
#include <iostream>
using namespace std;
int main(){
//栈内存数据 函数调用结束 自动删除
int a = 1;
// 堆内存数据(开辟空间 存数据 接收空间 需要手动删除回收资源)
// new int; // 开辟空间new 类型
// new int(10);// 存数据10
int* p = new int(10); // 指针接收空间数据10
// 分布实现
// int* p1 = nullptr;
// p1 = new int;
// *p1 = 10;
cout << "堆内存解引用数据:" << *p << endl; // 10
// 栈内存释放内存
delete p;
p = nullptr; //p 不再指向堆内存空间
if (p!= nullptr){
cout << "堆内存数据:" << *p << endl;
}
//数组与堆内存
int* p_arry = new int[10];
cout << "第0个元素:" << *p_arry << endl;
cout << "第1个元素:" << *(p_arry+1) << endl;
// 栈内存释放删除数组空间
delete[] p_arry;
return 0;
}
/* c++动态内存分配 new 数据类型(数据) 删除释放delete
* new
* 1.new 自定义类型 默认执行类的构造函数
* 2.成功申请内存空间 返回内存空间地址 用相应类型指针接收
* 3.申请失败 则抛出bad_alloc异常
* 4.本质是c 中malloc实现
*
* delete
* 1.delete 自定义类型 默认执行类的析构函数
* 2.释放内存 不会修改指针 指针依然指向原来的地址
* 3.重复delete 会出现异常
* 4.本质是c 中free实现
*
*
* 内存区划分
* 1.栈内存:局部变量 函数的参数等 空间小 调用函数结束回收资源 有独立的回收机制 存活周期短
* 2.堆内存:手动申请变量内存(new) 空间大 不存在回收机制 手动回收(delete) 存活周期长
* 3.代码区:存放执行代码 只读常量
* 4.共享内存区:高效I/O映射方式 用于装载一个共享的动态内存库,用户可以使用系统接口创建共享内存 做进程间通讯
* 5.字符常量区:常量字符串
* 6.静态存储区: 全局数据 静态数据
*/
10.2 malloc free
#include <iostream>
using namespace std;
class A{
public:
A(){cout<<"构造"<<endl;}
~A(){cout<<"析构"<<endl;}
};
int main(){
// c动态内存
//1. malloc
//1.1 申请空间
// 自定义申请空间大小(字节)
malloc(4); // 申请4字节的空间大小
// 根据类型自动申请空间大小
malloc(sizeof(int));
//1.2 存值
int * pa = (int*) malloc(sizeof(int));
*pa = 1;
cout << "pa取值:" << *pa <<endl;
free(pa);
cout << "pa取值:" << *pa <<endl;
cout << "-----------------1---------------------"<<endl;
//2. free
A* paa = (A*) malloc(sizeof(A));
A a; // 构造 析构
*paa = a;
free(paa);
return 0;
}
/* c 动态内存 malloc --free
* malloc
* 1.申请空间 需要提供空间大小(字节)
* 2.申请成功空间返回void * 失败返回NULL即是空指针
* 3.不会执行自定义类型的构造函数
*
* free
* 1. 空指针可以多次释放 非空则不允许
* 2. 不会执行自定义类型的析构函数
*/
11. 模板
//函数模板
template <typename 模板名1, typename 模板名2,...>
返回类型 函数名(模板名 参数名){函数体}
//类模板(class == typename效果一致 单纯用于区分修饰的模板类型)
template <class T1, class T2, ...>
class 类模板名{
成员变量+成员函数
}
11.1 函数模板
#include <iostream>
#include <cstdarg> // ...作为函数形参接收可变参数
using namespace std;
// 弱化类型+模板重载
template <typename T, typename H>
T cal(T a, H b){
return a + b;
}
template <typename T, typename H, typename J>
J cal(T a, H b, J c){
return a+b+c;
}
// 可变参数(参数类型不变)
template <typename T>
T sum_all(double _a, initializer_list<T> li){
const T * begin = li.begin();
T all =0;
for(const T & n: li){
all += n;
}
return all;
}
// ...省略号函数模板(参数类型可变)
template<typename T>
void test(T t){ // 终止递归的最后一个元素
cout <<"0----->"<< t<<endl;
}
template<typename... S, typename T>
void test(const T& t, const S&... args){ //&:避免传参拷贝 使用引用 const可接收左右值
cout << "可变参数个数:" << sizeof...(S) <<endl;
cout << "1----->" << t <<endl; // 输出参数值
test(args...); // 递归调用打印其他参数
}
// ...作为形参
void cout_all(int a, ...){
va_list args_li; // 获取可变参数列表
int all=0;
int argvalue;
all += a;
va_start(args_li, a); // 指定开始循环获取的参数列表
do{
argvalue = va_arg(args_li, int); // 获取参数值
all += argvalue;
} while(argvalue!= 0); // =0终止循环遍历参数列表
va_end(args_li); // 结束可变参数的遍历
cout << "all=" << all <<endl;
}
int main(){
//模板编程(实质:重载过程)
//函数模板
//1.弱化参数类型
cout << cal(1.1, 1) << endl;
cout << cal(1.11, 1, 1.456) << endl;
cout << "-----------------------1-----------------------"<<endl;
//2.弱化参数个数(使用可变参数)
cout << "sum=" << sum_all(99.99,{1.1, 2.1, 3.1, 4.1, 5.1, 6.0}) <<endl;
cout << "-----------------------2-----------------------"<<endl;
//3.省略号在函数模板作为可变参数
test("aa", 3.14, 5.55, 666, 'q');
cout << "-----------------------3-----------------------"<<endl;
//4.省略号作为函数形参
cout_all(1, 2, 3, 4, 0);
}
/* 模板编程实际上就是重载过程
* 函数模板(重新构建一个新函数 弱化参数类型 参数传递个数)
* 1.重新构建一个新函数
* 2.弱化了参数类型 并且函数模板也可以重载
* 3.弱化参数个数(使用可变参数|python:*args):initializer_list(类型一致) 或省略号(类型可不同)
* 4.可变参数initializer_list<类型唯一> 解引用获取参数值
* 5.省略号参数->可变参数应用函数模板 必须放置在形参最后 否则会导致递归函数执行编译错误
*
*/
11.2 类模板
#include <iostream>
using namespace std;
// 类模板
template <typename T>
class DIY_Vector{
// 数组接收数据
T c[10];
int i=0; //下标索引
public:
DIY_Vector()=default;
DIY_Vector(initializer_list<T> li){
for(auto v :li){
c[i] = v;
i++;
}
cout << c << endl;
}
T at(int id){
return c[id];
}
int size(){
return i;
}
T operator[](int id){
return c[id];
}
void push(T v){
c[i] = v;
i++;
}
};
//继承下类模板
template <class T1>
class F{
public:
T1 a;
F(T1 a):a(a){cout<<"F有参构造"<<endl;}
~F(){cout<<"F析构"<<endl;}
};
// 子类不是模板类(强制指定父类类型)
class S1: public F<int>{
public:
S1(int b):F(b){cout<<"S1有参构造"<<endl;}
~S1(){cout<<"S1析构"<<endl;}
};
// 子类是模板类
template<class T1>
class S2: public F<T1>{
public:
S2(T1 c):F<T1>(c){cout<<"S2有参构造"<<endl;}
~S2(){cout<< "s2析构"<<endl;}
};
int main(){
// 模板类(多应用于多变容器)
//1. 模拟vector
DIY_Vector<int> c;
c.push(11);
c.push(22);
c.push(33);
cout << "c.size():" << c.size()<< endl;
cout << "c.at():" << c.at(0)<< endl;
cout << "c[]:" << c[2]<< endl;
cout << "----------------1---------------------"<<endl;
//2. 继承下模板类
F<int> f(11);
S1 s1(22);
S2<string> s2("abc");
cout << f.a << "\t" << s1.a << "\t" << s2.a << endl;
return 0;
}
12. 文件
在 UNIX/Linux 平台中,用文本方式或二进制方式打开文件没有任何区别。
在 UNIX/Linux 平台中,文本文件以\n(ASCII 码为 0x0a)作为换行符号;而在 Windows 平台中,文本文件以连在一起的\r\n(\r的 ASCII 码是 0x0d)作为换行符号。
在 Windows 平台中,如果以文本方式打开文件,当读取文件时,系统会将文件中所有的\r\n转换成一个字符\n,如果文件中有连续的两个字节是 0x0d0a,则系统会丢弃前面的 0x0d 这个字节,只读入 0x0a。当写入文件时,系统会将\n转换成\r\n写入。
也就是说,如果要写入的内容中有字节为 0x0a,则在写人该字节前,系统会自动先写入一个 0x0d。因此,如果用文本方式打开二进制文件进行读写,读写的内容就可能和文件的内容有出入。
因此,用二进制方式打开文件总是最保险的 ----转载于http://c.biancheng.net/view/311.html
c++ 标准库提供读写文件方式
1. >> || << 读写文件:适用于以文本形式读写文件 ios::in | ios::out
2.read() || write() :适用于二进制形式读写文件 ios::out | ios::binary
12. 1 fstream 读取文件
#----test
i/o test...
你好 i/o
测试数据
11, 22, 33, 44
55, 66, 77, 88
#---- main.cpp
#include <iostream>
#include <fstream> // 文件流
#include <sstream> // 字符流stringstream
#include <string> // 提供stoi() 字符串转整形 || to_string 整形转字符串
using namespace std;
int main(){
//读取文件
//1.创建文件流对象
fstream fs("../3_other/test");
//2.打开文件
if(fs.is_open()){
//3.1 read()读取文件(中文编码问题)
// char c[100];
// fs.read(c, 100);
// cout << "读取数据:" << c <<endl;
cout<<"-----------------------"<< endl;
//3.2 getline()读取文件(不会出现编码问题)
string content;
// getline(fs, content,'\n'); // 默认读取到'\n'结束
// cout <<"读取的单行数据:" << content <<endl;
cout<<"-----------------------"<< endl;
//3.3 多行输出
// while(getline(fs, content)){
// cout <<"读取的数据:" << content <<endl;
// cout<<"-----------------------"<< endl;
// }
//3.4 读取数据再处理
while(getline(fs, content)){
stringstream ss; // 创建字符流
ss << content;
string s; // 单独一个数据
while(getline(ss, s, ',')){
cout << s <<"\t";
}
cout << endl;
}
}else{
cout <<"文件打开失败"<<endl;
}
fs.close(); // 关闭文件
return 0;
}
12. 2 fstream 写入文件
#----test1
xxx
#----main.py
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
int main(){
//写入数据
//数据准备
vector< vector<int> > arr{
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
fstream fs("../3_other/test1", ios::out);
if(fs.is_open()){
//1.write(数据, 数据长度)写入数据
fs.write("c++", 1);
//2. << 写入数据 不限定长度
fs<<"c++ is no.1" << endl;
//3. 写入较复杂数据
for(vector<int> vi: arr){
for(int v :vi){
fs<<v;
if(v!=vi[vi.size()-1]){
fs<<",";
}
}
if(vi != arr[arr.size()-1]){
fs << "\n";
}
}
}else{
cout << "文件打开失败"<<endl;
}
fs.close();
return 0;
}
/* 文件打开模式
* 1.ios::app 追加模式 所有写入追加到文件末尾
* 2.iso::ate 文件打开定位到末尾
* 3.ios::in 打开文件用于读取
* 4.ios::out 打开文件用于写入 覆盖之前写入的数据
* 5.ios::trunc
*/
12.3 fstream 定位文件位置
#----test2
abcdefg
hijklmx
#----main.cpp
#include <iostream>
#include <fstream>
using namespace std;
int main(){
//创建文件流
fstream fs("../3_other/test2");
if(fs.is_open()){
//文件内容定位
char c, c1, c2;
fs.seekg(3L, ios::beg); // 定位读取数据(字节)
fs>>c;
cout << "从开头定位读取的数据:" << c << endl;
fs.seekg(-1L, ios::end);
fs>>c1;
cout << "修改前从末尾定位读取数据:" << c1 << endl;
fs.seekp(-1L, ios::end); // 定位修改数据(字节)
fs.put('z');
fs.seekg(-1L, ios::end);
fs.get(c2);
cout << "修改后从末尾定位读取数据:" << c2 << endl;
}else{
cout <<"打开失败"<<endl;
}
fs.close();
return 0;
}
/* seekp() 定位写入数据
* seekg() 定位读取数据
* 文件定位模式
* ios::beg 从文件头开始偏移
* ios::end 从文件尾开始偏移
* ios::cur 从当前位置开始偏移
*
* get(char* s, streamsize n, char delim) || put():逐个读写单个字符(可指定读取长度字符串)
*/
12.4 二进制格式 文件读写
#include <iostream>
#include <fstream>
using namespace std;
int main(){
//二进制写入文件
char arr[5]{1, 2,3,4,5};
ofstream f1("../3_other/test2", ios::out| ios::binary);
f1.write((char*)&arr, sizeof(arr));
f1.close();
//二进制读取文件
ifstream f2("../3_other/test2", ios::in | ios::binary);
f2.read((char*)&arr, sizeof(arr));
for(int i:arr){cout << i << "\t" << endl;}
f2.close();
return 0;
}
13.多任务
13.1 线程
#include <iostream>
#include <thread>
using namespace std;
void test(int id){
cout << "Thread-test..."<< id << endl;
cout << "test函数线程id:" << this_thread::get_id() << endl;
}
void test1(int id){
bool flag = true;
int i = 0;
while (flag){
cout << "test1..."<< id << endl;
i++;
if(i>10){
//break;
//return;
flag = false; // 结束线程
}
}
}
int main(){
//线程
//1.创建线程 且自动启动
thread t(test, 11);
// 主线程等待子线程执行完毕
// t.join();
// 主线程与子线程分离 子线程失去所有权 各自执行 主线程结束 子线程强制结束
t.detach();
cout << boolalpha << "t线程所有权:" << t.joinable() << endl;
// 2.线程id
cout << "t-id:" << t.get_id() <<endl;
cout << "Main-test..."<<endl;
cout << "主函数线程id:" << this_thread::get_id() << endl;
// 3.手动结束线程
thread t1(test1, 22);
t1.join();
}
/* 线程
* 1. 创建线程并启动 thread t;
* 2. t.join() 等待子线程结束
* 3. t.detach() 主线程与子线程分离 各自执行 主线程结束 子线程结束
* 4. 使用detach() 通过joinable()判断线程所有权
* 5. 指定线程id获取 t.get_id(); 当前线程id获取 this_thread::get_id();
* 6. 系统休眠 #include <zconf.h> sleep(1)
* 7. 线程休眠 std::this_thread::sleep_for || std::this_thread::sleep_until
*/
13.2 多线程
#include <iostream>
#include <string>
#include <thread>
#include <zconf.h>
#include <mutex> // 互斥量|互斥元
using namespace std;
// 1.mutex
mutex m;
void test(string t){
for (int i = 0; i < 10; ++i) {
m.lock();
cout << t << ">>" << i << endl;
// sleep(1);
m.unlock();
}
}
int m1 = 100;
void get1(){
while(1){
// 2. lock_guard<>
lock_guard<mutex> lg(m);
if(m1>=0){
cout << "get1>>>" << m1 <<endl;
m1-= 10;
sleep(1);
} else
break;
}
}
void get2(){
while(1){
// 2. lock_guard<>
lock_guard<mutex> lg(m);
if(m1>=0){
cout << "get2>>>" << m1 <<endl;
m1-= 10;
sleep(1);
} else
break;
}
}
int main(){
// 多线程
// 1.mutex 互斥量
thread t1(test, "thread1");
thread t2(test, "thread2");
cout << t1.native_handle() << endl;
cout << t1.get_id() << endl;
t1.join();
t2.join();
cout << "主函数线程1结束"<< endl;
cout << "-----------------------------------"<< endl;
// 2. lock_guard
thread t3(get1);
thread t4(get2);
t3.join();
t4.join();
cout << "主函数线程2结束"<< endl;
cout << "-----------------------------------"<< endl;
//3. unique_lock
cout << "主函数线程3结束"<< endl;
return 0;
}
/* 默认多线程并发 公有资源会出现资源竞争
* 1.mutex互斥量 针对的是公有共享资源 需要手动上锁 解锁 不允许发生拷贝构造和移动构造
* 2.lock_guard<mutex> 能够自动解锁 构建对象时 调用m.lock() 销毁对象 调用m.unlock();
* 3.unique_lock<mutex> 类似lock_guard<mutex> 提供更好的上锁 解锁操作
*/
13.3 线程与成员函数
#include <iostream>
#include <thread>
using namespace std;
// 全局函数
void test11(){
cout << "全局函数test11"<< endl;
}
class A{
public:
static void test22(){
cout << "静态成员函数test22" << endl;
}
void test11(){
cout << "成员函数test11" << endl;
}
};
int main(){
// 线程修饰全局函数
thread t1(test11);
t1.join();
cout << "主函数线程1结束"<< endl;
cout << "---------------------------"<< endl;
// 线程修饰成员函数
A a1;
thread t2(&A::test11, a1);
t2.join();
cout << "主函数线程2结束"<< endl;
cout << "---------------------------"<< endl;
// 线程修饰静态成员函数
thread t3(A::test22);
t3.join();
cout << "主函数线程3结束"<< endl;
return 0;
}
13.4 条件变量
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex m; // 互斥量
condition_variable cv; // 条件变量
int n =0;
void sender(){
//发送方
for (int i = 0; i < 30; ++i) {
unique_lock<mutex> ul(m);
n += 10;
cout << "s>>>存储10 当前n=" << n <<endl;
if (n>=20){
cout << "s>>>n>=20 通知接收者"<<endl;
cv.notify_all();
}
ul.unlock();
}
}
void receiver(){
//接收方
for (int i = 0; i < 30; ++i) {
unique_lock<mutex> ul(m);
cout << "r>>>查看n总量"<<endl;
if(n<20){
cout << "r>>>n<20 等待发送通知"<<endl;
cv.wait(ul);
}
n-=20;
cout << "r>>>接收20 当前n="<< n <<endl;
ul.unlock();
}
}
int main(){
// 线程-条件变量
thread t1(sender);
thread t2(receiver);
t1.join();
t2.join();
cout << "主函数线程结束"<< endl;
}
// 条件变量 保证线程同步(线程间按照指定顺序执行)
14. 拷贝
// 深浅拷贝浅谈
#include <iostream>
using namespace std;
// 浅拷贝问题
class test{
public:
int *pid = nullptr;
test(int* pid):pid(pid){
cout << "构造函数执行-test" << endl;
}
// 构造拷贝函数
test(const test& t){
// 1.浅拷贝(默认)
pid = t.pid; // 成员数据单纯拷贝值(共享一个空间)
cout << "拷贝构造函数执行-test" << endl;
}
~test(){
cout << "析构函数执行-test" << endl;
}
};
//深拷贝
class test1{
public:
int *pd = nullptr;
test1():pd(pd){
cout << "无参构造构造函数执行-test1" << endl;
}
test1(int* pd):pd{pd}{
cout << "构造函数执行-test1"<<endl;
}
//拷贝构造函数
test1(const test1 &t){
// 2.深拷贝(初始化指针成员属性:开辟新空间+添加数据+赋值指针)
pd = new int(*t.pd); // 成员数据独立空间 互不影响
cout << "拷贝构造函数执行-test1"<<endl;
}
};
// 普通类test2用于验证拷贝触发
class test2{
public:
test2(){
cout<< "无参构造函数执行-test2"<<endl;
}
test2(const test2& s){
cout<< "拷贝构造函数执行-test2"<<endl;
}
~test2(){
cout<< "析构函数执行-test2"<<endl;
}
};
//对象作为函数传递参数
void fn(test1 t){ //传递的参数为对象
cout <<"fn调用结束" <<endl;
}
//对象作为函数的返回值
test2 fn1(){
test2 t;
return t; // 返回值为对象
}
int main(){
//拷贝
//1.浅拷贝(共享空间)
printf("---------------浅拷贝---------------\n");
int* pt = new int(3);
test t(pt);
test t1 = t; // 拷贝对象(执行拷贝构造函数)
cout << "t.pid=" << *t.pid << endl;
cout << "t1.pid=" << *t1.pid << endl;
printf("--------------浅拷贝修改数据后----------------\n");
*t.pid = 33; // 存在问题:修改对象成员数据 另一个对象成员数据亦修改
cout << "t.pid=" << *(t.pid) << endl;
cout << "t1.pid=" << *t1.pid << endl;
delete pt;
//2.深拷贝(独立空间)
printf("--------------深拷贝----------------\n");
int* ptt = new int(4);
test1 tt(ptt);
test1 tt1 = tt; // 拷贝对象
cout << "tt.pd=" << *tt.pd << endl;
cout << "tt1.pd=" << *tt1.pd << endl;
printf("--------------深拷贝修改数据后----------------\n");
*tt.pd = 44; // 解决:修改对象成员数据 另一个对象成员数据不影响
cout << "tt.pd=" << *tt.pd << endl; //
cout << "tt1.pd=" << *tt1.pd << endl;
delete ptt;
//3.拷贝触发
//3.1 对象作为函数参数传递
printf("------------拷贝触发:对象作为函数参数传递------------------\n");
fn(tt); // out:拷贝构造函数执行-test1
//3.2 对象作为函数返回值
printf("------------拷贝触发:对象作为函数返回值------------------\n");
test2 ttt = fn1(); // 默认out:无参构造构造函数执行-test2(编译器为了避免拷贝生成临时对象消耗内存 优化拷贝动作不显示)
//手动编译(不经过编译器优化)
//cmakelists.txt添加set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors")
//out:
//无参构造函数执行-test2 :创建对象test2 t
//拷贝构造函数执行-test2 : 拷贝函数内对象t给保存在临时空间的对象t1
//析构函数执行-test2 :删除函数fn1内对象t
//拷贝构造函数执行-test2 :拷贝临时空间对象t1给ttt
//析构函数执行-test2 :main函数调用结束删除临时对象t1和对象ttt
//析构函数执行-test2
return 0;
}
/* 浅拷贝 拷贝值 (对象成员有指针成员 指针值亦拷贝)
* 深拷贝 额外开辟空间进行存储 对象之间成员数据不影响 对象占用独立的空间 仅针对对象成员有指针有实际应用
* 拷贝触发:参数传递||对象指向已有对象||对象作为参数传递||对象作为返回值
* 不要返回栈内存的地址 局部变量(地址||引用) 编译会出错
* 避免编译器自动优化 手动编译:
* 1.命令行加参数-fno-elide-constructors
* $>g++ -std=c++11 main.cpp -fno-elide-constructors
* 2.cmakelist.txt cmake编译配置
* set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors")
*/
15 STL(标准模板库)
C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈
C++ STL 核心组件
1.容器(封装了数据结构的模板类 ps:vector<> list<> queue<>)
2.算法(算法作用于容器 大多被设计成模板函数 提供容器各种操作方式 排序 修改 添加等)
3.迭代器(遍历对象集合[容器]的元素来完成读写操作)
4.适配器(提供适应多接口适用的模板类)
5.内存分配器(为容器类模板提供自定义内存申请和释放操作)
15.1 序列容器(顺序容器)
序列容器:存储数据类型为基本数据类型 容器内部元素排序与值无关 与添加进的顺序相关 但是多数容器均可使用下标索引访问(模板函数实现运算符重载)
vector<int> i{1,2,3}; i.at(0);
| 容器 | 描述 |
|---|---|
| string | 与vector相似,尾部插入|| 删除速度快 |
| array | 固定大小数组,支持快速随机访问,不能添加和删除 |
| vector | 可变大小数组,支持快速随机访问,在尾部之外的位置插入和删除 速度慢 |
| deque | queue | 双端(区分前后)队列,支持快速随机访问,两端插入和删除速度快 |
| forward_list | 单向链表、只支持单向顺序访问,插入和删除快,查询和更新慢 |
| list | 与单向链表不同,它是双向链表,其他一样。 |
#include <iostream>
#include <list>
#include <deque>
using namespace std;
int main(){
//顺序容器
//1.list
cout<<"----------list----------"<<endl;
list<int> l1 {1, 2, 3};
// 1.1 list 增
l1.push_front(4);
l1.push_back(5);
// 1.2 删
l1.remove(1);
// 1.3 改
l1.front()=55;
cout<< "元素总个数:"<< l1.size() <<endl;
cout << "第一个元素"<< l1.front() <<endl;
l1.back() = 66;
cout << "最后一个元素"<< l1.back() <<endl;
// 1.4 查
cout <<"所有元素-迭代器方式:";
for (auto i = l1.begin(); i != l1.end(); i++){
cout<< *i <<"\t";
}
cout<< "\n";
cout << "从大到小排序:" << ""<<endl;
l1.sort();
for (auto i = l1.begin(); i != l1.end(); i++){
cout<< *i <<"\t";
}
cout<< "\n";
cout<<"----------deque----------"<<endl;
deque<int> d;
// 增
d.push_back(11);
d.push_back(22);
d.push_back(33);
d.push_back(44);
// 删
d.pop_front();
// 改
d.at(1) = 55;
*d.begin() = 111;
*(d.end()-1) = 444;
cout << "元素总个数:" << d.size() <<endl;
cout << "第一个元素:" << d.at(0) << " " << d[0] << " " << d.front() << endl;
cout << "最后一个元素:" << d.back() <<endl;
// 查
cout << "所有元素:";
for(int v:d){
cout << v << "\t";
}
cout <<endl;
cout << "所有元素-使用迭代器";
deque<int>::iterator v = d.begin();
while (v != d.end()){
cout << *v << "\t";
v++;
}
cout << endl;
}
15.2 关联容器(排序容器)
关联容器:存储数据形式<key:value>(树存储结构) 会根据键值大小对数据进行升序排序
可定义排序
map<int, less<int>> || map<int, greater<int>>
| 容器 | 描述 |
|---|---|
| map | **key值唯一 可以通过key修改value ** |
| multimap | 区别于map multimap可存储多个K值相同的键值对 |
| set | K=V 且 唯一 键 值均不能修改 |
| multiset | key=value 但可以重复 键 值均不能修改** |
#include <iostream>
#include <map>
#include <string>
#include <set>
using namespace std;
int main(){
//pair(键值对)
cout << "--------------pair----------------"<<endl;
pair<string, int> p("aa", 24); // 生成键值对元素
cout << p.first << " " << p.second <<endl;
// 关联容器
//2.map
cout << "--------------map----------------"<<endl;
map<int, string> stu;
//增
stu.insert({0, "aa"});
stu.insert(make_pair(1, "bb"));
stu.insert(pair<int, string>(2, "cc"));
stu.insert(pair<int, string>(3, "dd"));
stu.insert(pair<int, string>(3, "ee")); // key相同 添加失败
//删
stu.erase(0); // 删除指定key元素
// stu.clear(); // 删除所有元素
//改
stu.at(1) = "bbb";
stu[2] = "ccc";
// 查
cout << "key=1 vaule=" << stu.at(3) <<endl;
cout << "key=1在容器出现的次数:" << stu.count(3) <<endl;
cout << "元素总个数:" << stu.size() <<endl;
cout<< "容器是否为空:" << stu.empty() << endl;
cout<< "所有元素:";
for(pair<int, string> p1: stu){
cout << p1.first << ":" << p1.second << "\t";
}
cout<<endl;
cout << "--------------multimap----------------"<<endl;
//3.multimap
multimap<int, string> multi_m;
multi_m.insert({1, "aa"});
multi_m.insert({0, "aa"});
multi_m.insert({0, "bb"}); // 增加数据
cout<<"元素总个数:" << multi_m.size()<<endl; // 容器元素个数
cout<<"出现次数:" << multi_m.count(0) <<endl; // key值出现次数
//multi_m.erase(0); // 删除key=0的所有value值
//multi_m.clear(); // 删除所有元素
cout << "所有元素:";
for(auto i=multi_m.begin();i != multi_m.end(); ++i){
cout << i->first << ":" << i->second <<"\t";
}
cout<<endl;
// 4.set
cout << "--------------set----------------"<<endl;
set<int> s;
// 增
s.insert(111);
s.insert({222, 333, 444, 555, 555});
// 删
s.erase(111); // 删除第一个元素
//s.erase(s.begin());
// 查
cout<< "元素总数:" << s.size() <<endl;
cout << boolalpha << "容器是否为空:" << s.empty() <<endl;
cout << "迭代器方式-所有元素:";
for(auto i=s.begin(); i != s.end(); ++i){
cout << *i << "\t";
}
cout << endl;
cout << "--------------multiset----------------"<<endl;
multiset<int, greater<int>> ms;
//增
ms.insert(10);
ms.insert({9, 1, 3, 4, 2, 2, 2});
// 删
ms.erase(3);
//ms.erase(2); // 删除相同元素 会全部删除2
//查
cout << "元素总数:" << ms.size() <<endl;
cout << "第一个元素:" << ms.begin().operator*() <<endl;
cout << "最后一个元素:"<< ms.end().operator*() <<endl;
cout << boolalpha<< "容器是否为空:" << ms.empty() <<endl;
cout << "迭代器方式-所有元素:";
for(auto i=ms.begin(); i != ms.end(); i++){
cout << *i << "\t";
}
cout << endl;
return 0;
}
/* 关联容器
* 1.关联式容器在存储元素,默认会根据键值的大小做升序排序
* 2.通常可直接通过键 获取 值
* 3. map multimap
* 4. set multiset 默认k=v 所有相当于直接存储v 按照v升序排序
* 5. 排序:set<int, less<int>> 默认升序 set<int, greater<int>> 降序
* map:key值必须唯一
* multimap:可存储k值相同的多个键值对
* set: 键与值相同 且不重复 键值 均不能修改
* multiset: 键与值相同 可以重复 键值 均不能修改
*/
15.3 无序容器(hash容器)
hash容器(c++>11支持):类比关联容器 存储数据键值形式<k:value>(hash表存储结构) 但是不会进行存储数据的升序排序 即数据是无序的
| 容器 | 特征 |
|---|---|
| unordered_map | pair<k,v>形式数据存储 k值不可重复 键值对无序 |
| unordered_multimap | 可存储多个相同的k值的键值对 |
| unordered_set | 元素唯一(k=v) 存储元素无序 |
| unordered_multiset | 可存储相同元素(k=v) 存储元素无序 |
#include <iostream>
#include <unordered_map>
#include <string>
#include <unordered_set>
using namespace std;
int main(){
//无序容器
//1.unordered_map
cout << "--------------unordered_map---------------"<<endl;
unordered_map<int, string> uom;
//增
uom.insert({3, "cc"});
uom.insert({1, "aa"});
uom.insert({2, "bb"});
//删
//uom.erase(1);
// 改
uom.at(1) = "aaa";
// 查
cout<<"元素总数:" << uom.size() <<endl;
cout<< "所有元素:"<<endl;
for(pair<int, string> p: uom){
cout << p.first << ":" << p.second<<"\t";
}
cout << endl;
//2.unordered_set
cout << "--------------unordered_set---------------"<<endl;
unordered_set<int> uos;
//增
uos.insert(1);
uos.insert({3,3, 1, 4, 0, 9,5});
// 删
uos.erase(1);
// 查
cout << "元素总数:" << uos.size() <<endl;
cout << "迭代器方式-所有元素:"<<endl;
for(auto iter= uos.begin(); iter != uos.end(); iter ++){
cout << *iter << "\t";
}
cout << endl;
}
15.4 迭代器
迭代器多用于容器遍历元素 实则是对指针的操作
begin() end() 返回一个迭代器对象
begin().base() 返回一个指向容器第一个元素的指针
*(容器.begin().base()) 解引用取值
#include <iostream>
#include <vector>
using namespace std;
int main(){
//迭代器
vector<int> s{1, 2, 3, 4};
auto begin = s.begin();
auto end = s.end();
cout << "begin=" << *begin <<endl;
cout << "解引用begin=" << *begin.base() <<endl;
cout << "end=" << *(end-1) <<endl;
cout << "解引用end=" << *(end.base()-1) <<endl;
// 迭代器-遍历容器
for(auto i = s.begin(); i < s.end(); ++i ){
cout << *i << "\t";
}
cout<<endl;
}
/* begin() 返回一个(指向容器第一个元素)迭代器对象
* begin().base() 返回指向容器的第一个元素的指针
* *s.begin(): *重载 解引用获取第一个元素指针所指向的值
*/
16 make
编译过程
源文件(.cpp) ---[1预编译]-->预编译(c/c++)文件(.i)---[2编译优化]-->编译(汇编语言)文件(.s)---[3汇编]-->二进制文件(.o)---[4链接]-->可执行(二进制)文件(.exe)
源代码(source coprede)→预处理器(processor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→链接器(Linker)→可执行程序(executables)
1.预编译处理
g++ -std=c++11 -E 源文件 (生成.i预处理文件)
2.编译优化
g++ -std=c++11 -S 源文件 (生成.s汇编文件)
3.汇编
g++ -std=c++11 -c 源文件 (生成.o二进制文件)
4.链接
通过连接器将多个目标(链接库.o文件)文件链接在一起生成一个可执行程序(.exe)
通常头文件放置在include文件 动态链接库多为跟源文件相关联的cpp文件打包
动态链接库:g++ -fPIC -shared xx.cpp -o xx.dll (windows)
静态链接库:ar -crv libtest.a xx.o (linux) 生成静态链接库libtest.a

16.1 make编译
make 是一条计算机指令 可以从makefile(Makefile)文件中获取构建程序所依赖的关系
windows-mingw: mingw32-make -f 指定makefile文件 目标(或省略)
linux: make -f 指定makefile文件 目标(或省略)
16.2 makefile编译
# makefile格式
# target: 编译源文件
# tap键 命令(g++ -std=c++11 编译源文件)
#------------------------------------------------------->
# 针对一个源文件进行编译
# test: a.cpp
# g++ -std=c++11 a.cpp -o test # 生成test.exe可执行文件
#------------------------------------------------------->
# 针对多个源文件一起进行编译
#include xx.make $(aaa) # include 可以添加引用其他makefile文件 或者是变量aaa
target1: a.o b.o # 编译目标
# -o 指定可执行文件名
g++ -o test a.o b.o
# 可省略command:g++ -std:c++11 -c a.cpp
a.o: a.cpp
b.o: b.cpp
# 伪目标 不要放置在开头 用于删除编译过程产生的文件 使用make clean指令删除||mingw32-make clean
.PHONY:clean
clean:
del a.o b.o # windows 可以使用通配符 del *.o *:任意字符出现次数>=0 || ?:字符出现次数==1 || [...]: 匹配特定字符
#rm a.o b.o # linux
# target 编译目标
# clean 删除编译过程产生的编译文件
# terminal执行指令
# $make target(指定编译目标) (linux) # -f 指定makefile文件
# $mingw32-make target (windows)
16.3 makefile实例
target: a.o b.o
g++ -o test a.o b.o
a.o: a.cpp
b.o: b.cpp
.PHONY:clean
clean:
del *.o
//----a.h
#ifndef CODE_A_H
#define CODE_A_H
#include <iostream>
#include <string>
using namespace std;
class animal{
public:
string name;
animal(string name):name(name){}
void eat();
};
#endif //CODE_A_H
//----b.cpp
#include <iostream>
#include "a.h"
using namespace std;
void animal::eat() {
cout << name << "正在进食"<<endl;
}
//----a.cpp
#include <iostream>
#include "a.h"
using namespace std;
int main(){
cout << "中文编码测试"<< endl;
cout << "c++ test..."<<endl;
cout << "---------------"<<endl;
// 多个源文件进行编译
animal dog("狗狗");
dog.eat();
return 0;
}

17 cmake
17.1 cmake简介
cmake就是一个具有自己编译规则的编译工具 针对的是不同的编译器 使编译过程规范化
Clion: cmake-cmakelist.txt
qmake: xx.pro
#-----CmakeList.txt
# cmake的最低版本
cmake_minimum_required(VERSION 3.16)
# 编译的项目
project(code)
# 当前编译使用c++14版本标准来编译程序
set(CMAKE_CXX_STANDARD 14)
# 项目的执行程序 括号中的test 是最终生成的可执行程序名称 后面的是程序的源码文件
add_executable(test main.cpp stu.cpp)
17.2 cmake使用
#----主项目工程cmakelist.txt
cmake_minimum_required(VERSION 3.16)
project(code)
set(CMAKE_CXX_STANDARD 14)
# 主项目工程加载项目工程
add_subdirectory(test)
#----子项目工程cmakelist.txt
add_executable(test a.cpp b.cpp)
set(A 10) # 设置变量
# 打印输出
message("a=${A}") # 输出信息 a =10
set(A ${A} 20)
message("a=${A}") # 输出信息 a=10;20
17.3 cmake实例
//----a.h
#ifndef CODE_A_H
#define CODE_A_H
#include <iostream>
#include <string>
using namespace std;
class animal{
public:
string name;
animal(string name):name(name){}
void eat();
};
#endif //CODE_A_H
//----b.cpp
#include <iostream>
#include "a.h"
using namespace std;
void animal::eat() {
cout << name << "正在进食"<<endl;
}
//----a.cpp
#include <iostream>
#include "a.h"
using namespace std;
int main(){
cout << "中文编码测试"<< endl;
cout << "c++ test..."<<endl;
cout << "---------------"<<endl;
// 多个源文件进行编译
animal dog("狗狗");
dog.eat();
return 0;
}
# 通常链接库是关联源代码cpp文件 的其他cpp文件
# 通常链接库放置在文件夹lib 头文件放置在include
# b.cpp 为关联cpp文件(可以多个) a.cpp为程序入口源文件 a.h为头文件
# 动态链接库
# 编译过程不会被链接到代码 在可执行文件运行时载入 可共享相同的链接库 只需要一份该库实例 实现进程中的资源共享.so文件 且程序升级较容易 只需要重新编译.so文件即可 但是可移植性差 因为环境不同 动态库存放位置不一样 导致程序运行失败
# linux动态链接库文件: xx.so + xx.h
# Windows动态链接库: .lib引入库文件(提供dll位置 编译期间加载) .dll动态库文件(提供程序所需数据等 程序运行) .h 头文件
g++ -fPIC -shared b.cpp -o b.dll(windows)
g++ -fPIC -shared b.cpp -o b.so(linux)
编译:g++ -o test a.cpp -L 动态链接库目录(-l 动态链接库文件名)
# 静态链接库(一组.o文件的集合)
# 静态库链接.a文件 打包到.exe文件 且生成的可执行文件不依赖静态链接库 装载速度块 但是浪费空间和资源
g++ -c b.cpp 生成b.o文件
ar -crv libtest.a b.o(linux)
编译:g++ -o test a.cpp -L 指定静态库的搜索路径(-l 不需要lib前缀和.a后缀)
# g++(gcc) 编译参数
-shared :指定生成动态链接库
-static :指定生成静态链接库
-std: 指定c++标准
-fPIC :表示编译为位置独立的代码 用于编译共享库 它们可以放在可执行程序的内存里的任何地方
-L :连接的库所在的目录
-l:指定链接时需要的动态库 编译器查找动态连接库时有隐含的命名规则 即在给出的名字前面加上lib 后面加上.a/.so来确定库的名称
-Wall :生成所有警告信息
-ggdb :此选项将尽可能的生成gdb的可以使用的调试信息
-g :编译器在编译的时候产生调试信息
-c :只激活预处理 编译和汇编 也就是把程序做成目标文件(.o文件)
-Wl,options :把参数(options)传递给链接器ld。如果options中间有逗号 就将options分成多个选项 然后传递给链接程序
17.4导入动态链接库编译
CLION编译器
#----主工程项目code/cmakelist.txt
cmake_minimum_required(VERSION 3.16)
project(code)
set(CMAKE_CXX_STANDARD 14)
# 加载子工程项目cmakelist.txt
add_subdirectory(test1)
#----子工程项目test/camkelist.txt
# 导入头文件
include_directories("C:/Users/FSH/Desktop/test/include")
# 添加执行程序
add_executable(test1 a.cpp)
# 导入依赖库
# 方式一(外部记得配置添加环境变量path=C:/Users/FSH/Desktop/test/lib)
#target_link_libraries(test1 C:/Users/FSH/Desktop/test/lib/b.dll)
# 方式二
find_library(lib_b_dir b C:/Users/FSH/Desktop/test/lib) # lib_b_dir任意变量名 b:为.dll前的名
message("bb=${lib_b_dir}")
target_link_libraries(test1 ${lib_b_dir})

浙公网安备 33010602011771号