c++基础思维导图2
结构体
结构体的基本概念:用户自定义的数据类型
结构体定义和使用
- struct 结构体名{结构体成员}
- struct 结构体名 变量名;
struct 结构体名 变量名 = {成员1,成员2}
定义式struct 可以不加上分号,但是创建一个结构体数组的时候需要加上分号
定义结构体的关键字是struct,不可以省略;
关键字struct 可以省略;
结构体变量利用操作符" . "访问成员
结构体数组
- struct 结构体名 数组名{元素个数}={{},{}.{}...{}}
结构体和指针
- 操作符->可以通过访问结构体属性
- Student s1;
Student *p=&s1;
结构体嵌套结构体
结构体做函数的参数
- 如果修改主元素的数据就地址传入,不修改就按值传入;
- 函数中的参数改为指针为减少内存空间而不会复制新的版本出来
小作业:通讯录管理系统
程序中的内存模型
针对c++面向对象技术的详细讲解
c++程序在执行的时候大方向分为四个区域
-
1、代码区
- 存放函数体二进制代码,由操作系统进行管理的
- 代码区是共享的
- 代码区是只读的
-
2、全局区
- 存放全局变量和静态变量以及常量
- 全局区还包括常量区,字符串常量和其他常量
- 该区域里面的数据结束后由操作系统释放
-
3.栈区
- 由编译器自动分配释放。存放函数的参数值,局部变量,局部常量等
- 栈区的数据在函数执行之后会自动的释放,所以不要返回局部变量的地址
-
4.堆区
-
由程序员分配和释放,若程序员不释放,程序结束时操作系统回收
-
new
- 例子:
-
#include
#include
#include
using namespace std;
int *fun_c(){
int * p =new int(10);
return p;
}
int main(){
int * p = fun_c();
cout <<* p<<endl;
//打印的结果是10;
return 0;
}
- new返回的是数据类型的指针
- delete
- 释放内存
- 释放数组的时候使用delete[]
不同区域存放的数据赋予了不同的生命周期,给我们更大的灵活编程
c++中的引用
引用的作用就是给变量起别名
语法:数据类型 &别名=原名
例子
- #include
#include
using namespace std;
int main(){
int a = 10;
int &b =a;
b = 20;
cout << "a =
"<<a<<endl;
cout << "b =
"<<b<<endl;
return 0; //结果是a=b=20;
}
- #include
#include
using namespace std;
int main(){
int a = 10;
int &b =a;
int c = 30;
b = c;
cout << "a =
"<<a<<endl;
cout << "b =
"<<b<<endl;
cout << "c =
"<<b<<endl;
return 0; //结果是a=b=c=30;
}
注意
- 引用必须要初始化
- 引用初始化后不可以改变
引用做函数参数
- 作用:函数传参时,可以利用引用的技术让形参修饰实参
- 优点:可以简化指针修改实参
函数传递
- 地址传递
- 值传递
- 引用传递
引用做函数的返回值
- 不能返回局部变量的引用,可以使用静态变量的引用
引用的本质就是一个指针常量,引用一旦初始化后就不可以发生改变
常量的引用
-
const int & ref = 10;
- 加上const之后编译器将代码修改:
int temp = 10;
const int & ref = temp;
- 加上const 变为只读状态不可以修改
-
int & ref =10是不合法的
-
例子
- #include
using namespace std;
void show(int&);
int main(){
int a =10;
show(a);
cout << a <<endl;//改变了a 的值,输出的结果是1000;
return 0;
}
void show(int &b){
b =1000;
}
- 作用就是传递函数的形参的时候使用const int &就不会改变原来传入的变量的值
函数高级
函数的默认参数
-
如果调用函数的时候传入了参数的值就优先使用传入的值,没有传入就使用默认参数
-
如果某个位置已经有了默认参数,那么这个位置往后,从左到右都必须要有默认值
- 错误示范:
#include
using namespace std;
int fun(int,int,int);
int main(){
int t =fun(10,20,30);
cout << t <<endl;
return 0;
}
int fun(int a=10,int b,int c){//这里的a已经有了默认值那么所有的形参都应该设置默认值,否则就会报错
return a+b+c;
}
- 声明有了函数的默认值实现就不能有(声明和实现只能有一个默认参数),为了避免二义性
函数占位参数
-
就是写函数形参的时候只是写数据的类型
-
占位参数还可以有默认参数
- 例子:
int fun(int a,int b,int= 20);
int main(){
int t =fun(5,10);
cout << t <<endl;
return 0;
}
int fun(int a,int b,int c){
return a+b+c;
}
函数的重载
-
函数名可以相同,提高复用性
-
需要满足的条件
- 同一个作用域下;
- 函数名称相同;
- 函数参数类型不同或者个数不同或者顺序不同;
-
注意:函数的返回值不可以作为函数重载的条件;
-
引用作为重载条件,默认参数产生重载的歧义
函数重载的注意事项
类和对象
c++面向对象三大特性:封装、继承、多态
具有属性的事物叫做对象,具有相同性质的对象可以抽象成类
对象特性
-
构造函数和析构函数
-
构造函数
-
构造函数是对象的初始化
-
构造函数主要作用是创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
-
语法
- 类名(){}
- 构造函数没有返回值也不写void
- 函数名称和类相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候自动调用构造,不需要手动调用
-
-
析构函数
-
析构函数是清理
-
析构函数主要作用时对象销毁前系统自动调用,执行一些清理工作
-
语法
- ~类名(){}
- 析构函数没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号~
- 析构函数不可以由参数,因此不可以发生重载
- 程序在对象摧毁前会自动析构,无需手动调用只会调用一次
-
作用:析构函数可以将堆区开辟数据做释放操作
-
-
构造函数的分类及调用
- 按照参数分类:有参构造和无参构造;
- 按照类型分类:普通构造和拷贝构造
- 拷贝调用函数使用的时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传递
-
以值的方式返回局部对象
- 三种调用方式:括号法、显示法、隐式转换法
- 注意:1.匿名对象
特点:当前执行结束后,系统会理机回收掉匿名函数;
2.不要利用拷贝构造函数,初始化匿名对象
3.隐式转换法
- 构造函数和析构函数是编译器自动调用,完成对象初始化和清理工作
-
如果不提供析构和构造函数,编译器会提供,提供的都是空实现
-
初始化列表
-
example
-
Person():m_A(10),m_B(20),m_C(30){}
- Person(int a,int b,int
c):m_A(a),m_B(b),m_C(c){}
-
深拷贝和浅拷贝
-
浅拷贝:简单的赋值拷贝操作;
- 浅拷贝带来的问题就是堆区的内存重复释放
-
深拷贝:在堆区重新申请空间,进行拷贝操作
- 如果属性有在堆区开辟的,一定要自己提供拷贝函数,防止浅拷贝带来的问题
-
-
类对象作为类成员
- 当其他类的对象作为本类成员,构造时候先构造类对象
-
静态成员
-
静态成员就是成员表变量和函数前面加上关键字static,称为静态成员
-
静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
-
静态成员函数
- 所有对象共享同一函数
- 静态成员函数只能访问静态成员变量
-
-
成员变量和成员函数分开存储
- 在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
- 空对象占内存空间是1;
c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
- 空对象占字节数是1,为了表示对象的内存位置;对象中有静态成员变量,所占字节数还是1,说明了静态成员变量不属于类对象上,同理成员函数不属于类对象上
-
this指针
- this指针指向被调用的成员函数所属的对象
- this指针式隐含每一个非静态成员函数的一种指针
- this指针的用途:
1.当形参和成语那变量同名时,可以用this指针来区分;
2.在类的非静态成员函数中返回对象本身可使用return *this
-
空指针访问成员函数
- c++中空指针也是可以调用成员函数的,但是也要注意没有用到的this指针;如果用到this指针,需要加以判断保证代码的健壮性
-
const修饰成员函数
-
常函数
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时关键mutable,在常函数中依然可以修改
-
常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
-
友元
-
友元的关键字为friend
-
友元的目的就是为了一个函数或者类访问另一个类中私有成员
-
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
运算符重载
- 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
- 对于内置的数据类型的表达式的运算符是不可能改变的;
不要滥用重载;
-
(加减乘除)运算符重载
-
例子
- #include
-
using namespace std;
class Person{
public:
int m_a;
int m_b;
/* Person operator+(Person
&p){
Person temp;
temp.m_a =
this->m_a+p.m_a;
temp.m_b =
this->m_b+p.m_b;
return temp; //使用成员函数运算符重载
}*/
};
//使用全局函数运算符重载
Person operator+(Person &p1,Person &p2){
Person temp;
temp.m_a = p1.m_a+p2.m_a;
temp.m_b = p1.m_b+p2.m_b;
return temp;
}
void test01(){
Person p1;
p1.m_a = 10;
p1.m_b = 10;
Person p2;
p2.m_a = 20;
p2.m_b = 20;
Person p3 = p1+p2;
cout
<<p3.m_a<<endl;
cout <<
p3.m_b<<endl;
}
int main(){
test01();
return 0;
}
-
左移运算符重载
- 作用:可以输出自定义数据类型
-
递增运算符重载
- 作用:通过重载递增运算符,实现自己的整形数据
- 注意:前自增运算符重载时返回的时引用,后自增运算符重载时返回的时值,自减同理
-
赋值运算符重载
-
关系运算符重载
-
函数调用运算符重载
继承
继承的作用
- 作用:定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性,这时候可以考虑继承的技术,减少重复代码
- 派生类时从基类中继承过来的,一类是自己增加的成员,从基类继承过来的表现其共性,而新增的成员体现了其个性
继承语法
-
class 子类:继承方式 父类
class A:public B;
A类为子类或派生类
B类为父类或基类
继承的方式有三种
- 公共继承;
- 保护继承;
- 私有继承;
继承中同名处理方式
-
继承中同名的静态成员在子类对象上如何进行访问
- 访问子类同名成员--直接访问即可
- 访问父类同名成员,需要加作用域
-
继承中同名静态成员函数如何进行访问
- 和同名静态成员相同
-
继承的访问方式
- 通过对象进行访问
- 通过类名进行访问
-
子类中如果出现和父类相同的成员就会把父类中的成员全部隐藏,解决方法就是加上作用域
-
多继承语法
- 语法:class 子类:
继承方式 父类1, 继承方式 父类2 ... - 多类继承可能会引发父类中有同名成员出现,需要加作用域区分
- 在实际开发中不建议多用多继承
- 语法:class 子类:
-
菱形继承概念
-
两个派生类继承同一个基类
-
又有某个类同时继承连个派生类
-
这种继承被称为菱形继承或者钻石继承
-
菱形继承容易出现多继承二义性的情况,也容易浪费内存
-
利用虚继承,解决菱形继承问题,继承之前,加上关键字 virtual 变为虚继承,其中最开始的基类称为虚基类
- 其内部本质上是一个指针,用一个虚指针指向同一个数据
- 语法 class
类名: virtual public 要继承类名;
-
封装
意义:
1.将属性和行为作为一个整体,表现生活中的事物;
2.将属性和行为加以权限控制
权限控制
-
三种访问权限:
-
公共权限(public):成员类内可以访问,类外可以访问
-
保护权限(protected):成员类内可以访问,类外不能访问
- 子类可以访问父类保护的内容
-
私有权限(private):成员类内可以访问,类外不能访问
- 子类不可以访问父类私有的内容
c++中struct和class的区别
- 唯一的区别是默认的访问权限不同
- struct默认访问权限是公有
- class默认访问权限是私有
成员属性设置为私有的优点
-
将所有成员属性设置为私有,可以自己控制读写权限
-
对于写权限,我们可以检测数据的有效性
-
例子
- #include
#include
using namespace std;
class student{
public:
void set_name(string name){
m_name = name;
}
string get_name(){
return m_name;
}
int get_age(){
return age;
}
void set_classes(string
class1){
classes=class1;
}
private:
string m_name;//可读可写
int age=18;//可读
string classes;//可写不可读
};
int main(){
student st1;
st1.set_name("jerry");
st1.set_classes("english
education");
cout <<"学生的名字是:
"<<st1.get_name()<<endl;
cout <<"学生的年龄是:
"<<st1.get_age()<<endl;
return 0;
}
多态
多态分为两类
-
静态多态
- 函数重载和运算符重载属于静态多态,复用函数名
-
动态多态
- 派生类和虚函数实现运行时多态
静态多态和动态多态的区别
- 静态多态的函数地址早绑定-编译阶段就确定函数地址
- 动态多态的函数地址晚绑定-运行阶段确定函数地址
多态优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
- 组织结构清晰
在实际的开发中是开闭原则,对扩展进行开发,对修改进行关闭;
c++的开发中提倡利用多态设计组织架构,因为多态的优点有很多
纯虚函数和多态类
-
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写内容,因此可以将虚函数改为纯虚函数
-
纯虚函数的语法:virtual 返回类型 函数名 (参数列表) = 0;
-
当类中出现了纯虚函数,这个类也称为抽象类
-
抽象类的特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构和纯虚析构
- 多态使用时,如果子类中属性开辟堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
- 虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象;
2.都需要具有具体的函数实现;
虚构和纯虚析构区别:
1.如果纯虚析构,该类属于抽象类,无法实现实例对象
-
虚构语法:virtual ~类名(){}
-
纯虚构语法:virtual ~类名() =
0; -
总结
- 1.虚析构或者纯虚析构就是用来解决通过父类指针释放子类对象;
- 2.如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构;
- 3.拥有纯虚析构函数的类也属于抽象类;