-
-
C++知识点汇总
-
发表于: 2023-11-26 17:33 3208
-
一.C++入门
1. c++课程重要性
1. c++是面向对象的编程语言,面向对象是目前主流的开发思想
2. C++是后续QT的基础,QT是嵌入式领域图形开发框架
3. C++和QT相对独立,弯道超车的机会
4. C++和QT是一个就业方向
2. C++发展
1983年,贝尔实验室(Bell Labs)的Bjarne Stroustrup发明了C++。 C++在C语言的基础上进行了扩充和完善,是一种面向对象程序设计(OOP)语言。
C++的源文件扩展名是cpp
Stroustrup说:“这个名字象征着源自于C语言变化的自然演进”。还处于发展完善阶段时被称为“new C”,之后被称为“C with Class”。C++被视为C语言的上层结构,1983年Rick Mascitti建议使用C++这个名字,就是源于C语言中的“++”操作符(变量自增)。而且在共同的命名约定中,使用“+”以表示增强的程序。
常用于系统开发,引擎开发、嵌入式开发等应用领域, 至今仍然是最受广大程序员喜爱的编程语言之一。
C++在嵌入式中可以用于:
系统开发、算法开发、图形用户界面(GUI)开发。
3. C++的特点
1. C语言基础上,全面支持面向对象开发
2. 功能强大,应用领域广泛
3. 为数不多支持底层开发的面向对象语言
4. 在面向对象语言中,C++运行效率比较高
4. C++学习方法
1.C++知识点概念内容比较多,反复复习。
2.课程偏理论,有的内容不理解,可以先背下来,后续课程可能会理解更深
3.学好编程要多练习,简单的也要手写确认一遍,记笔记。
4.不懂的地方,有想自己测试的地方:做好标记,最重要的是继续,不要立刻查书、反复看代码。会错过后续内容,越来越听不懂;
5. 学习过程中不可能每个知识点都100%懂,有些内容可能经过多年的程序开发实践才领悟,所以即使有不懂的,也按进度计划前行
5. c和c++区别
做饭 洗衣服 扫地
c语言处理事情的逻辑:
1. 自己做饭
2. 自己洗衣服
3. 自己扫地
把事情事情分条目,一步步来做,这样是能够把握细节,符合计算机的思维,特点是运行效率高,开发效率低
C++语言处理事情的逻辑:找个对象让TA来做。
调用不同的对象来处理不同的事物,符合人的思维,特点运行效率低,开发效率高。
C语言的方式怎么来做,C++考虑的是找谁来做
6.软件安装
按照流程一直下一步,需要注意按照路径不能有中文。按照完成找到下方内容打开
6.1更改编码
重启就可以了,不用再看是否编码改成GBK
6.2 创建新项目
下一步完成
6.3 运行
点击下方图标运行 或者是Ctrl + R
6.4 添加c++11支持
QMAKE_CXXFLAGS+=-std=c++11
第一个程序
代码对齐快捷方式 ctrl + i
#include <iostream> //输入和输出的头文件 using namespace std; //使用标准的命名空间 int main() { cout << "你好" << endl; //cout输出 endl:end line 换行 int a=10; char ch='A'; cout< cout< //输入 // int b; // cin>>b; // cout< //c++中字符串 string 是C++内置的一个专门用于处理字符串的类。代替C语言的char * string s="hello"; string s2("world"); //c++中写法 //传统的遍历方式 for(int i=0;i cout< } cout< //c++11中的遍历方式 for each。 for(char ch:s2){ cout< } return 0; }
二.引用 (掌握)
1.引用概念
概念:相当于给变量取个别名
操作引用就相当于操作变量,注意引用类型和变量类型保持一致
性质1:成为一个变量的引用之后,就不能成为其他变量的引用
#include <iostream> //输入和输出的头文件 using namespace std; //使用标准的命名空间 int main() { int a=10; int& small_a=a; cout<<&a<<" "<<&small_a<<endl; //0x61fe84 = 0x61fe84 int b=20; small_a=b;//这里是把b的值给了small_a cout << a << " " << small_a << endl; //20 20 cout << &a << " " << &small_a << endl; //0x61fe84 0x61fe84 &small_a=b; //错误:不允许再成为其他变量的引用 return0; }
性质2:引用必须初始化并且不可以为NULL
#include <iostream> //输入和输出的头文件 using namespace std; //使用标准的命名空间 int main() { int& a; //错误:引用必须初始化 int & b=NULL; //错误:引用初始化不能为NULL return0; }
性质3:当作为纯数字的引用时,需要加const修饰。代表其引用的数值不可更改
#include <iostream> using namespace std; int main() { //int & a=100; //错误:修饰纯数字未加const const int& a=100; return 0; }
性质4:const修饰的引用,不可以更改变量值。但是变量值更改之后,引用的值是会跟着变化
#include using namespace std; int main() { int a=100; const int& b=a; //b++; //错误:只读不允许修改值 a++; cout << a << " " << b << endl; //101 101 return 0; }
引用的应用:
C++中参数的传递有两种:
1. 值传递 :只是实参的一个拷贝,形参改变不会影响实参的值
2. 引用传递 : 不会产生拷贝副本,相当于操控的变量本身
引用传递可以提高参数的传递效率,因为引用传递就不需要给形参开辟空间,也不需要为其赋值。可以操作使用变量。
如果只是想取到变量值,不做变量值的修改,可以const修饰引用,避免不小心更改到变量的值
三.函数提高
1. 内联函数inline (熟悉)
关键字inline 修饰的函数,取代c语言中的宏定义
作用:编译期会把函数体的内容展开到调用处,节省了函数调用的时间,提高了效率
使用内联函数的条件:
1. 函数体内容少(5行以内)
2. 函数运行时间短,不能复杂的循环
3. 调用频繁
2.函数的默认值(掌握)
函数中参数允许有默认值,如果这个参数不传值,就用默认值。如果传入值,会把默认值覆盖掉。默认值可以提高程序的灵活性。
函数默认值的注意事项:
1.函数声明和定义分离时
声明和定义分离的时候,函数默认只需加在声明处
void draw(string color = "black"); //函数声明 void draw(string color){ cout << "当前画笔颜色:" << color << endl; }
2.函数有多个参数时
当函数有多个参数时,“向后原则”:其中一个参数有默认值,其后的参数都需要有默认值。
void test(int a,int b=20,int c=0){ cout<<a<<" "<<b<<" "<<c<<endl; } int main() { test(10,30); //10,20,30 test(5); //5,20,0 test(10,20,30); //10,20,30 cout <<endl; }
3.函数的重载(掌握)
C++中允许函数重名,处理相同或者相似的业务逻辑。但是需要参数列表不同,参数的个数不同,参数的类型不同,参数的顺序不同进行区分。但是不能通过返回值不同进行区分。
void show(int a){ cout << a << endl; } void show(string buf){ cout << buf << endl; } void show(int a,int b){ cout << a << " " << b << endl; } int main() { show(20); show("LOL"); show(10,30); cout << endl; }
函数重载的注意事项:
函数重载不要和函数默认值一起使用。容易产生错误
练习:写一个计算工资的函数getSalary. 普通文员工资只有基本工资base_salary. 销售的工资包括基本工资base_salary和奖金bonus 。 函数重载形式给出这两个函数
#include using namespace std; void getsalary(int base_salary) { cout << "文员工资:" << base_salary << endl; } void getsalary(int base_salary,int bonus) { int sum = base_salary + bonus; cout << "销售工资:" << sum << endl; }
二、面向对象基础
面向对象的三大特征:
封装、继承、多态
类与对象
物以类聚,人与群分
类:同一类事物(对象)共同特征提取出来,就形成了类。类是抽象的概念
对象:由类创建的具体实体
类中包括属性和行为
属性:描述类的数据,一般都是名词2
行为:描述类的具体操作,一般都是动词
人: 属性:姓名 性别 年龄
行为:吃喝睡 学习 睡觉
电脑: 属性:品牌 型号
行为:播放音乐 播放视频 运行游戏
1.类的实现
#include using namespace std; class Computer{ //class是类的关键字 Computer是类名 public: //公共权限 ,类内和类外都可以访问 string brand; string model; void play_music(){ cout<<"《只因你太美》"<<endl; } void play_video(){ cout<<"《家有儿女》"<<endl; } void run_game(){ cout<<"LOL"<<endl; } void show(){ cout<<"品牌:"<<brand<<" 型号:"<<model; } };
2.类的实例化(掌握)
由类创建对象的过程叫类的实例化
对象分为两种:
栈内存对象:
用 . 的方式调用成员,出了作用范围(花括号之间)自动销毁
int main() { Computer c1; c1.brand = "Dell"; c1.model = "G5"; //由对象调用方法 . c1.play_music(); c1.play_video(); c1.run_game(); c1.show(); }
堆内存对象:
用new关键字创建,对象类型的指针(Computer *)指向new关键字开辟的空间。使用->
方式进行调用,堆内存对象需要成员手动销毁,如果不销毁就会造成内存泄漏,造成程序的卡顿。用
delete关键字进行销毁,并把对象指针置位NULL
int main() { Rectangle * r2 = new Rectangle; cout << "请输入矩形的长:" << ""; cin >> r2->length; cout << "请输入矩形的宽:" << ""; cin >> r2->width; r2->perimeter(r2->length,r2->width); r2->area(r2->length,r2->width); delete r2; r2 = NULL; }
3.封装(掌握)
通常情况下,会把类中的属性或者特征进行封装。通常是把类中的属性变为私有(private)。如果外部访问需要公共接口,可以控制属性的读和写的权限。提高了程序的安全性
4.构造函数(重点)
用于创建对象时给属性值进行初始化。
构造函数是个特殊的函数:
1. 没有返回值值类型,也不可以写void
2. 构造函数与类同名
3. 如果没有显示给出构造函数,编译器会给出默认的构造函数(参数为空,并且函数体为空)并没有实际操作,如果给出任意构造函数,系统默认构造函数不存在
无参构造函数
有参构造函数
有参构造函数让对象的创建更加灵活
#include using namespace std; class Computer{ private: string brand; string model; int weight; public: void show(){ cout<<brand<<" "<<model<<" "<<weight<<endl; } //Computer(){} //编译器默认的构造函数 Computer(string b,string m,int w){ brand=b; model=m; weight=w; } }; int main() { Computer c1("联想","pro16",200); c1.show(); }
构造函数支持函数重载
#include using namespace std; class Computer{ private: string brand; string model; int weight; public: void show(){ cout<<brand<<" "<<model<<" "<<weight<<endl; } //构造函数支持重载 Computer(string b,string m,int w){ brand=b; model=m; weight=w; } Computer(){ brand="HP"; model="暗影精灵"; weight=100; } }; int main() { Computer c1("联想","pro16",200); //调用有参构造函数 c1.show(); Computer * c2=new Computer; //调用无参构造函数 c2->show(); delete c2; c2=NULL; Computer * c3=new Computer("联想","air15",150); c3->show(); delete c3; c3=NULL; }
构造函数支持函数默认值
同样遵循函数默认的注意事项
练习:幼儿园老师kindergarene,属性有姓名和性别,性别默认是女 。要求封装,给出姓名的可读和可写接口。
然后给出show方法 打印出其中的属性值
构造初始化列表
是构造函数简便写法 ,对象属性简单赋值时推荐初始化列表
5. 拷贝构造函数(掌握)
概念:用已存在对象的值来初始化新的对象属性值
拷贝构造的特点:
1. 拷贝构造函数与构造函数构成重载
2. 如果不显示写出拷贝构造函数,编译器会给出默认的拷贝构造函数,完成对象之间的值复制。如果显示写出拷贝构造,编译器就不会提供默认的拷贝构造
拷贝构造函数的形式:
1. 与类同名
2. 参数是对象的引用或者const修饰对象的引用
对象之间相互独立的,对象之间属性也是相互独立的
深拷贝和浅拷贝
浅拷贝:编译器默认给出的拷贝构造函数,完成的就是浅拷贝。会完成对象之间简单的值复制。但是如果对象的属性有指针类型时,浅拷贝也只会简单的地址值的复制,这时两个对象的指针保存同一块地址,指向了同一块内存。破坏了对象之间的独立性
浅拷贝
深拷贝
当对象属性有指针类型时,对象指针变量需要指向自己独立的区域,拷贝内容代替拷贝地址
6. 析构函数(掌握)
析构函数:对象销毁前做善后清理工作。在对象销毁时,之前为成员变量开辟的内存空间需要进行释放
形式: ~类名() {}
析构函数特点:
1. 与类同名 ,因为没有参数,所以不能重载
2. 不显示给出析构函数,会有默认的析构函数,函数体为空。给出析构函数,编译器就不提供默认析构函数
3. 对象销毁时自动调用
如果类中都是基本数据类型和string类型,这时可以不显示写出析构函数,对象的数据会随着对象的销毁而销毁
当类中有指针类型的变量时候,这时需要写出析构函数,释放为指针变量开辟的空间
之前深拷贝,析构完善的代码:
7. 作用域限定符的使用
7.1. 名字空间(熟悉)
命名空间实际上是由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间区域,把一些自己定义的变量、函数等标识符存放在这个空间中,从而与其他实体定义分隔开来。
std是C++标准库的一个名字空间,很多使用的内容都是来自于标准名字空间,例如字符串std::string、std::cout...
当项目中包含using namespace std;时,代码中使用std名字空间中的内容就可以省略前面的std::
7.2 函数声明和定义分离时(重点)
函数声明和定义分离时需要用,类名::指明函数属于哪个类,指明函数范围
void Student : : setAge(int a)
void 返回值
Student : : 类的作用域限定符
setAge 函数名
8. explicit关键字(掌握)
等号赋值时,等号左侧是对象类型,右侧恰好是对象构造函数所需要的类型,这时就会把右侧值传入到构造函数中,相当于隐式调用构造函数。
但是编码过程中,可能会不小心,隐式触发构造函数。造成错误,所以可以用explicit关键字屏蔽隐式构造
9. this指针(掌握)
概念
this指针是个特殊的指针,存储是对象的首地址,成员函数(类内)都隐含一个this指针
原理
类的成员函数都隐含一个this指针。哪个对象调用成员函数,this指针就指向哪个对象,访问哪个对象的属性。虽然不用手写this指针,但是编译器都会使用this指针来调用成员
this指针应用
1.区分同名参数与属性(掌握)
可以用this指针来区分属性 和 参数
this指针区分属性和参数
2.链式调用(熟悉)
当返回值是对象引用时,可以返回*this ,此函数支持链式调用
10. static关键字
静态局部变量(熟悉)
static关键字修饰的局部变量。
特点:类中所有对象共享,所在函数第一次调用时初始化,程序结束时销毁
静态成员变量(掌握)
定义:static关键字修饰的成员变量
1. 必须得类内声明,类外定义
2. 所有对象共享,程序运行时创建,程序结束时销毁
3. 公共权限下,除了用对象访问静态成员变量,也可以使用 Test(类名)::访问静态成员变量
#include <iostream> using namespace std; class Test{ public: static int a; //类内声明 }; int Test::a = 10; //类外初始化 int main() { cout << Test::a << endl; //通过 类名:: 直接调用(10) Test t; cout << t.a << endl; //10 t.a++; Test t1; cout << t1.a << endl; //11 return 0; }
静态成员函数(掌握)
定义:static修饰的成员函数就是静态成员函数。
1. 对象产生之前就可以调用。(普通成员函数必须需要对象来调用)
2. 静态成员函数只可以访问静态成员,不可以访问非静态成员,因为静态成员函数没有this指针。
3. 静态成员函数声明和定义分离时,static只需加在声明处
#include <iostream> using namespace std; class Test{ public: static int a; int b = 2; void test(){ cout << b << endl; } static void testStatic(){ //cout << b << endl; //静态成员函数不能访问普通变量 //test(); //静态成员函数不能访问普通函数 cout << a << endl; } }; int Test::a = 10; int main() { Test::testStatic(); //对象产生之前可以调用 return 0; }
练习:封装一个ClassRoom类,最后输出教室的总面积和总个数
11.const关键字
通常表示只读,不可修改。可以保证数据的安全
常局部变量
const修饰的局部变量。不可修改其数值
void func(const int a){
//a++; //错误,只读不允许修改
}
常成员变量(掌握)
const修饰的成员变量
1. 运行时其值不可以修改
2. 必须通过初始化列表赋予初始值,不能通过构造函数方法赋值
常成员函数
const修饰的成员函数
1. 可以访问非const成员变量,但不可以更改变量值
2. 不可以访问非const成员函数
常对象
const修饰的对象
第一种方式: const 类名 对象名
第一种方式: 类名 const 对象名
常对象不允许修改属性值,只能访问const函数
三.运算符重载
类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的数据成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
友元可以用在运算符重载
1.友元函数(掌握)
友元函数在类外部的普通函数,需要类中用friend关键字进行说明。友元函数可以访问类中的私有成员
特点:
1. 因为不是类内函数,没有this指针。参数是对象的引用(Person &p),用于访问类中成员
2. 友元函数不受类内权限修饰符的限制,友元函数的声明可以放在任意位置(private/public)
#include using namespace std; class Person{ private: //手机号 微信号 地址 string phoneNumber; string weChat; string address; public: Person(string p,string w,string a){ phoneNumber=p; weChat=w; address=a; } friend void friendMethod(Person& p); }; void friendMethod(Person& p){ cout<<p.phoneNumber<<" "<<p.weChat<<" "<<p.address<<endl; } int main() { Person p1("180xxx1100","180xxx1100","银荷大厦"); friendMethod(p1); }
2.友元类(熟悉)
当一个类是另一个类的友元类时,它就可以访问另一个类中所有的成员。需要friend关键字在另一个类中做说明
友元关系:
1. 单向性
2. 不具有传递性
3. 不能继承
3.运算符重载(掌握)
在C++中,运算符的操作对象只能是基本数据类型。自定义类型是不能直接使用,所有运算重载对已有的运算符赋予多重含义。使同一个运算符作用与不同类型数据做出不同的行为
运算符重载的本质是函数重载,它也是C++多态的一种体现
运算符重载增强了C++的可扩充性,使得C++代码更加直观、易读
C++提供的运算符重载机制,重载运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要重载的运算符共同组成。和其他函数一样,重载运算符的函数也包括返回类型、参数列表及函数体,
可以被重载的运算符:(了解)
算术运算符:+、-、*、/、%、++、--
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
不被重载的运算符:
成员运算符 .、指针运算符 *、三目运算符 ? :、sizeof、作用域 ::
+运算符友元函数重载
++操作符友元函数重载
用int做参数占位,代表后置++
成员函数都有一个隐含this指针。可以比友元函数重载少一个参数,隐含的this指针作为成员函数的第一个参数存在
+运算符的成员函数重载
++操作符的成员函数重载
赋值运算符重载
创建一个类编译器会提供默认的构造,析构和拷贝构造,除此之后,还有赋值运算符重载
作用:用一个对象的值改变另一个对象的值
形式: T&operator=(const T& other) ;// T可以是任意类型
特点:是类内函数,只支持成员函数重载
不显示给出赋值运算符函数时,编译器会给出默认的赋值运算符函数,完成对象间的赋值。
但是当属性有指针类型的时候,这时需要显示写出赋值运算符函数,类似于浅拷贝出现的问题
#include using namespace std; class Integer{ //整数类 private: int value; public: Integer(int value):value(value){} void show(){ cout<<value<<endl; } Integer& operator =(const Integer& other){ cout<<"赋值运算符函数调用"<<endl; this->value=other.value; } }; int main() { int a=10,b=20; a=b; //b的值给a Integer one(10); Integer two(20); one=two; //赋值 // one.show(); //20 }
类型转换运算符函数
作用:把自定义类型转成任意类型
特点:1.只支持成员函数重载
2.不需要写返回值
运算符重载注意事项:
●运算符重载限制在C++已有的运算符范围内,不允许创建新的运算符。
●运算符重载也是函数重载,运算符也是函数。
●重载之后的运算符不能改变优先级和结合性。
●重载之后的运算符不能改变操作数和语法结构。
●运算符重载不能改变该运算符用于基本数据类型的含义,但是可以把基本数据类型与自定义类型一起运算,或者都使用自定义类型。
●运算符重载是针对新类型数据的实际需要对原有运算符的功能进行扩充,因此重载之后的功能应该与原有的功能类似,避免没有目的地使用运算符重载。
四.模板和泛型编程
模板
函数模板(掌握)
c++使用模板可以参数多态化,可以不指定确切的数据类型来实现函数和类的通用实现,提高了代码重用性
template 模板的关键字
typename 后面的参数代表任意类型
函数模板使用注意事项:
1. typename和class都可以作为模板参数的说明
2. 使用新的函数模板,就需要重新加上模板的声明
类模板(熟悉)
注意:类模板不支持自动类型推导,创建对象指明实际的参数类型,<>
typename通常用于函数模板 class用于
类模板声明和定义分离
string字符串(掌握)
string是C++内置的一个类,内部对char * ,进行了封装实现。不用担心数组越界问题
构造函数
string(); //创建一个空的字符串 例如: string str;
string(const char* s); //使用字符串s初始化
string(const string& str); //使用一个string对象初始化另一个string对象
string(int n, char c); //使用n个字符c初始化
字符串追加
string& operator+=(const char* str); //重载+=操作符
string& operator+=(const char c); //重载+=操作符
string& operator+=(const string& str); //重载+=操作符
string& append(const char *s); //把字符串s连接到当前字符串结尾
string& append(const char *s, int n); //把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s); //同operator+=(const string& str)
string& append(const string &s, int pos, int n);//字符串s中从pos开始的n个字符连接到字符串结尾
取
char& operator[](int n); //通过[]方式取字符
char& at(int n); //通过at方法获取字符
插入
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
删除
string& erase(int pos, int n = npos); //删除从Pos开始的n个字符
string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串。注意接收
替换
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串
3.泛型编程
泛型编程提出的目的是:发明一种语言机制,能够实现一个标准的容器库(标准模板库 STL),标准容器库可以做到编写一般化的算法,来支持不同的数据类型。
标准模板库(STL)是美国的惠普实验室开发的一系列软件的统称,后来被引入到C++语言中,主要包括:
● 算法 例如:swap交换函数 sort排序
● 容器 :存放数据的类模板
● 迭代器:指示元素位置。方便算法访问操作容器
在STL中,几乎所有的代码都采用了模板来实现,相比于传统的库,具有更好的适用性和重用性。
容器分为:顺序容器 和 关联容器
顺序容器: 元素之间顺序关系,元素有固定位置
关联容器: 元素之间没有严格物理上的顺序关系,内部会做相应排序
容器的分类:(掌握)
顺序容器:Array数组 vector向量 list 列表
关联容器: map键值对 mulitmap多重键值对
容器使用时,需加入相应头文件
Array数组 (熟悉)
c++11引入的数组,更安全和易于使用
#include
#include
using namespace std;
int main()
{
int a[5];
array arr1; //随机值
array arr2={10,20}; //10 20 0 0 0 0 0 0 0 0
arr2[0]=60; //60 20 0 0 0 0 0 0 0 0
for(int i:arr2){
cout<<i<<" ";
}
}
双向迭代器支持的操作:
it++, ++it, it--, --it,*it, itA = itB,itA == itB,itA != itB
随机访问迭代器支持的操作:
在双向迭代器的操作基础上添加
it+=i, it-=i, it+i(或it=it+i),it[i], itAitB, itA>=itB 的功能。
可以向前向后跳过任意个位置,可以直接访问容器中任何位置的元素
vector向量(掌握)
vector向量容器是一种支持高效的随机访问和高效尾部插入新元素的容器。
向量容器一般实现为一个动态分配的数组,向量中的元素连续地存放在这个数组中
因此对向量容器进行随机访问,具有和动态访问数组类似的效率。
向量容器具有扩展容器的功能,当数组的空间不够时,向量容器对象会自动new一块更大的空间,使用赋值运算符将原有的数据复制到新空间,并将原有空间释放。因此向量容器在每次扩展空间时,实际分配的空间一般大于所需的空间
可以高效的访问读取,但是插入和删除效率偏低
构造函数
vector v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end()); //将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem); //构造函数将n个elem拷贝给本身。
vector(const vector &vec); //拷贝构造函数
push_back(ele); //尾部插入元素ele
pop_back(); //删除最后一个元素
insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele
erase(const_iterator pos); //删除迭代器指向的元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
clear(); //删除容器中所有元素
at(int idx); //返回idx所指的数据
operator[idx]; //返回idx所指的数据
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素
list列表(掌握)
list内部实现是双向链表。由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移(++)和后移(--),属于双向迭代器
可以高效的插入和删除,但是不支持随机访问。
vector向量的方法在list双向链表里同样可以使用,但注意list不可以通过[]或者at方式访问数据
map 容器 (熟悉)
map里的元素都是对组pair。pair中第一个元素为key(键值),第二个元素为value(实值)
pair两种创建方式:
pair p ( value1, value2 );
pair p = make_pair( value1, value2 );
map容器特点:
1.map所有元素都会根据元素的键值自动排序
2、可以根据key值快速找到value值
3.map不允许容器中有重复key值元素
multimap允许容器中有重复key值元素
五.面向对象的核心
1.继承(掌握)
概念:在已有类基础上,创建新的类。拥有已有类的一些特性,通常在此基础上新增新的属性和方法,或更改已有类的一些实现
已有的类称为父类或者基类
新的类称为子类或者派生类
通过继承可以提高代码的复用,提高开发效率
如果子类中重定义了父类中同名的函数,会把父类中同名函数隐藏。访问时默认是子类中的方法。可以通过父类加作用域限定符的方式访问父类中被隐藏的方法
2.继承的注意事项:(掌握)
子类不会继承父类的构造函数和析构函数
子类必须直接或者间接调用父类的构造函数,来完成继承部分的初始化。如果子类中不写明如何调用父类的构造函数,这时调用父类无参的构造函数,如果父类不存在无参构造,这时就会报错。
3. 继承中的构造函数:(掌握)
1. 透传构造
派生类(子类)的构造函数直接调用父类(基类)的构造函数
2. 委托构造
类中的一个构造函数调用另外一个构造函数。另一个构造函数调用了父类的构造函数,这时就间接调用父类的构造函数
4. 权限(掌握)
public 在本类中可以访问 在子类中可以访问 在全局中可以访问
protected 在本类中可以访问 在子类中可以访问 在全局中不可以访问
private 在本类中可以访问 在子类中不可以访问 在全局中不可以访问
5. 不同权限的继承
继承的方式有三种:public继承 protectd继承 private继承。
最常用的是public继承
不管哪种继承方式,基类中私有的内容,在子类中都不能直接访问
public 继承(掌握)
父类的成员 | 父类中public权限 | 父类中protected权限 | 父类中private权限 |
在子类中的访问权限 | 在子类中是public权限 | 在子类中是protected权限 | 能够继承下来,但是不能直接访问 |
protected 继承
父类的成员 | 父类中public权限 | 父类中protected权限 | 父类中private权限 |
在子类中的访问权限 | 在子类中是protected权限 | 在子类中是protected权限 | 能够继承下来,但是不能直接访问 |
private继承
父类的成员 | 父类中public权限 | 父类中protected权限 | 父类中private权限 |
在子类中的访问权限 | 在子类中是private权限 | 在子类中是private权限 | 能够继承下来,但是不能直接访问 |
6. 对象创建和销毁的过程(掌握)
类中有对象成员时
当类中的属性是个对象的时候,这个属性称为对象成员
先调用对象成员的构造函数,再类自己构造函数,析构的顺序相反。
#include using namespace std; class Engine{ public: Engine(){ cout<<"Engine的构造函数"<<endl; } ~Engine(){ cout<<"Engine的析构函数"<<endl; } }; class Car{ public: Engine e; //对象成员 Car(){ cout<<"Car的构造函数"<<endl; } ~Car(){ cout<<"Car的析构函数"<<endl; } }; int main() { Car c; }
继承时创建和销毁过程
构造先父后子,销毁时相反,先子类后父类
创建和销毁综合:
总结:
1.静态成员在程序运行时创建,程序结束时销毁
2. 相同部分创建,先父类后子类,销毁的顺序相反
"成父成子"
对象的创建和销毁会逐层调用最上层基类,上述过程,说明面向对象的开发的特点,执行效率低,开发效率高。
7. 多继承(掌握)
C++允许多继承,一个类有多个基类
#include using namespace std; class Bed{ public: void lay(){ cout<<"可以躺着"<<endl; } }; class Sofa{ public: void sit(){ cout<<"可以坐着"<<endl; } }; class SofaBed:public Bed,public Sofa{ }; int main() { SofaBed sf; sf.lay(); sf.sit(); }
多重继承的问题:
多继承实际开发中用的比较少,因为它带来的程序的复杂性和维护的困难度。
当多个基类中有同名函数时,多个同名函数都会被继承下来,直接访问会产生二义性,需要用基类::来进行区分,到底访问哪个基类的方法
菱形继承(钻石继承)
如果一个基类有两个派生类,这两个派生类又做为基类,派生出新的子类。这时子类如果访问间接基类的函数,这时也会产生二义性
解决方式1:
也可以通过基类加作用域限定符的方式区分同名函数
#include using namespace std; class Furniture{ //家具类 public: void show(){ cout<<"一个家具"<<endl; } }; class Bed:public Furniture{ public: }; class Sofa:public Furniture{ public: }; class SofaBed:public Bed,public Sofa{ }; int main() { SofaBed sf; //sf.show(); sf.Bed::show(); sf.Sofa::show(); }
解决方式2:
通过虚继承方式,关键字virtual这时就只会从最上层基类获得一份数据,避免了二义性问题。虚继承编译器内部做了相应处理,所以这种方式比普通继承方式效率低。
8. 多态(重点)
多态按照字面的意思可以认为是“多种状态”,可以简单概括为“一个接口,多种状态”,即程序在运行时动态决定调用的代码。
多态与模板的区别:模板针对不同的数据类型采用同样的策略,而多态针对不同的数据类型采用不同的策略。
静态多态也称编译时多态: 函数重载, 运算符重载
动态多态的三个条件:
1. 公有继承
2. 基类指针或基类引用指向派生类对象
3. 派生类覆盖基类的虚函数
公有继承情况下,基类的指针可以指向派生类地址,基类的引用可以用派生类初始化。
未实现多态的情况
没有实现运行时多态出现的问题:只是根据接口参数的类型,调用接口参数的方法,并不能根据传入的实际对象类型,调用实际对象的方法
函数覆盖(掌握)
函数覆盖与函数隐藏类似,但是函数覆盖可以通过虚函数来支持多态,一个成员函数使用virtual关键字修饰,这个函数就是虚函数。
函数覆盖与虚函数特点:
1.当函数覆盖成功时,虚函数具有传递性
2.C++11中可以在派生类的新覆盖的函数后增加override关键字来验证覆盖是否成功
3.成员函数与析构函数可以定义为虚函数,静态成员函数与构造函数不可以定义为虚函数。构造函数不能继承,也就没法覆盖重写
4.如果成员函数的声明与定义分离,virtual关键字只需要在声明时使用
关键词:
传递override来验证
成员析构虚函数
分离只需加声明
重载: 同一作用域(类),同名函数,参数的顺序、个数、类型不同都可以重载。函数的返回值类型不能作为重载条件(函数重载、运算符重载)
重定义:有继承,子类重定义父类的同名函数(非虚函数),参数顺序、个数、类型可以不同。子类的同名函数会屏蔽父类的所有同名函数(如果想访问父类隐藏的函数可以通过作用域解决)
重写(覆盖)︰有继承,子类重写父类的虚函数。返回值类型、函数名、参数顺序、个数、类型都必须一致。
多态的原理(熟悉)
当一个类中有虚函数,这个类就会有一张虚函数表,表中记录类中所有的虚函数位置。这个类的所有对象都有一个虚函数表指针,通过虚函数指针找到虚函数表,执行相应的虚函数。
当这个类被继承时,虚函数表同样会被继承。如果派生类重写了基类的虚函数,这时虚函数表会相应更新。
当基类的指针或者引用调用虚函数时。这时就会触发查表的过程,会根据指向的实际对象类型,找到不同对象类型的虚函数表,执行不同的虚函数。
多态练习:
ADC 和 AD(战士) 和 APC(法师) 都继承自Role类。Role有一个attack()虚函数,并未指明具体行为。需要在子类做出具体的实现。要求给出一个公共接口 test(),可以传入不同的对象类型,执行不同的attack()方法。
多态的注意事项:(掌握)
虚析构函数:
多态可能会造成内存泄漏:
delete时只会根据对象指针类型执行它的析构函数
解决方式:将基类的析构函数加上virtual关键字
9.抽象类(掌握)
抽象类只是个抽象的概念,并不与具体对象相关连。
作用:只是派生类提供一个框架的作用。
特点:不可以实例化对象,不能作为函数返回值和函数参数
抽象类里面一定有纯虚函数 有纯虚函数的类一定是抽象类
纯虚函数格式:vitual 函数名()=0;
注意:=0是不可以省略,后面也没有方法体
当抽象类的派生类只实现部分的纯虚函数,该派生类仍然是抽象类。
抽象类可以创建堆内存指针对象指向可实例化类,但可实例化类无法创建指针指向抽象类。
(大 --> 小 ,小 -\> 大)
六.异常处理(熟悉)
异常概念
程序运行时出现的问题,代码语法没有问题,但是出现了逻辑错误。当出现C++预定义的异常类型时,就会自动给出异常信息的提示。如果异常不处理,程序就会停止继续运行。
#include using namespace std; int main() { string s="hello"; s.at(10); //报出std::out_of_range cout<<"-------"<<endl; }
C++处理异常的两种方式
1. throw 抛出异常
基本使用
throw可以抛出任意类型对象的异常,抛给程序的调用者,异常需要调用者进行处理。
抛出自定义异常
#include <stdexcept> 标准异常的头文件
2. try...catch 捕获处理
try块中放置可能出现异常的的代码,catch捕获到之后做相应的处理。如果代码的异常没有被catch捕获到,这时程序也会停止
为了提供捕获的概率,可以先精准捕获,再宽泛的捕获。最后可以使用catch(...)的方式捕获任意类型异常
七.智能指针(熟悉)
C++语言中堆内存的对象在new之后创建,如果忘记delete则会产生内存泄漏的问题。在Java、C#等语言直接提供垃圾回收机制来处理不使用的对象,因此在C++98中引入了智能指针的概念,能使堆内存对象自动回收,并在C++11趋于完善。
原理:智能指针主要用于管理堆内存对象的生命周期,智能指针是位于栈内存的对象,当智能指针对象的生命周期结束后,会在析构函数里释放管理的堆内存对象的内存空间,从而放置内存泄露。
对象的指针位于栈,指向的内存在堆区。指针出了作用范围自动销毁,指向的内存不会自动销毁,需要delete手动销毁
智能指针需要引入头文件#include <memory>
自动指针 auto_ptr c++98,现已不推荐使用
唯一指针 unique_ptr c++11提出
共享指针 shared_ptr c++11提出
auto_ptr自动指针
auto_ptr的方法
auto_ptr的缺陷
在复制语义时(拷贝,等号赋值),会发生控制权的转移,造成一些问题
unique_ptr唯一指针
会对一块资源,拥有唯一控制权。内部屏蔽了复制语义的操作。
如果控制权需要转移要用move函数来实现。
shared_ptr共享指针
允许一个资源在多个智能指针之间共享,使用引用计数来判断多少个智能指针对象在使用资源,每增加一个智能指针在使用,引用计数就会加1。 当一个智能指针放弃资源使用时,引用计数就会减1。当引用计数减少为0说明这个资源没有被使用,这时才会销毁。
八. 补充内容:
1.nullptr
用来代替c语言中的NULL 。因为NULL在宏定义中本质是个0
2. auto自动类型推导
但是不能推导表达式和数组
3. 进制输出
4. 设置场宽
赞赏
- [原创]密码学基础知识汇总 7758
- [原创]Python知识点 2764
- [原创]python知识点汇总 2829
- [原创][推荐]Java基础知识梳理(详细) 3158
- C++知识点汇总 3209