1.输入输出流
在C中要想输入和输出 我们会经常用到
#include <stdio.h>
在C++中头文件的命名风格不用.h
#include <iostream>using namespace std;
为什么要用上面俩句话的解释(自己写的博客)
c++中 为什么要写<iostream>和using namespace std;-CSDN博客
cin cout
这俩个函数是在std命名空间下定义的
#include <iostream>
using namespace std;
int main() {int a;cin>>a;cout << "a的数值是:" << a << endl;return 0;
}
#include <iostream>
int main() {int a = 10;std::cin >>a;std::cout << "a的数值是:" << a << std::endl;return 0;
}
ceil()函数向上取整
round( )四舍五入取整
#include <iostream>using namespace std;
int main() {int a = 10;cout<<ceil(3/2.0)<<endl;return 0;
}
2.bool类型
c语言没有 是C++中的一种基本数据类型(内置数据类型)
只用一个字节
0为false
1为true
非零的数组都是true
3.对象的含义
一块有类型的内存
4.用new关键字申请堆区空间(delete)
我们在C语言中在堆区申请内存空间是用malloc 释放用free
如果不释放会造成这段内存但用以后无法被访问 造成内存的泄露
在c++用我们new 开辟堆区空间 delete释放堆区空间
#include <iostream>using namespace std;
int main() {//int* p = (int*)malloc(sizeof(int));C语言中int* p = new int;return 0;
}
new与malloc相同的是 都可以返回开辟这段空间的首元素的地址
都是不同点是 new返回的地址类型是自动匹配存储信息的内容的,而malloc是返回的空类型需要强制类型转换
#include <iostream>using namespace std;
int main() {//int *p=(int*)malloc(sizeof(int)*4);int* p = new int[4] {1,2,3,4};//在堆区中开辟了4个int这么大小的空间 并返回了它的首元素的地址for (int i = 0; i < 4;i++) {cout << p[i] << " ";}return 0;
}
注意在开辟空间的时候初始化 在c中用malloc是不可以的,但是C++的new可以实现这个功能
中间是没有等号的
如果不初始化里面的数据是随机数
delete
#include <iostream>using namespace std;
int main() {//int *p=(int*)malloc(sizeof(int)*4);int* p = new int[4] {1,2,3,4};//在堆区中开辟了4个int这么大小的空间 并返回了它的首元素的地址for (int i = 0; i < 4;i++) {cout << p[i] << " ";}delete[]p;//把p所指向的那段空间都释放了//delete p//只是把那段空间的第一个元素释放了,与free()不同//如果不是放p所指向堆区的那段空间 会造成内存的泄露p=NULL;return 0;
}
#include <iostream>using namespace std;
int main() {//int *p=(int*)malloc(sizeof(int)*4);int* p = new int(3);//初始化1个数据return 0;
}
1.什么是引用
在C++语法上来看,引用是给变量起了个别名,不占用空间
2.引用的格式
类型& 引用变量名 =引用实体
3.引用的注意事项
a.引用定义的同时必须进行初始化,且不能初始化为空
引用是给变量起别名,那前提肯定得有实体变量
引用是给变量起别名,给空起了个别名没意义
c.不可以改变引用关系
这与引用的实质有关系
引用的实质是一个指针常量,是一个指向方向固定的指针,所有它不能更改指向,也就不能改变引用关系
int& b=a;
int* const b=&a;
#include <iostream>using namespace std;int main() {int a = 5;int& b3;//引用必须初始化int& b2 = NULL;//引用不能初始化为空int& b1 = a;int* const b1 = &a;//本质 -->指针常量(指向不能改变,但是指向内存空间里面的内容可以改变)ncout << b1 << " " << *b2;return 0;
}
4.C++中的左值和右值
左值
可寻址、有变量名的值
右值
不可寻址、一般没有变量名的值
左值引用
int a=10;//a为左值 有变量名 可寻址(&x)可以找到地址
int& b=a;
右值引用
#include <iostream>
using namespace std;
int main() {int&& c = 10;//10为右值 不可寻址(&10错误) return 0;
}
万能引用
const 类型 引用名 =引用实体
int main() {const int a = 10;const int& b = a;const int& c = 10;return 0;
}
5.引用的应用
做函数的形参
参数的传递方式有俩种
值传递(只是一个传值的过程),但是传递的值可能是地址,也可能是基本类型数据
引用传递
#include <iostream>using namespace std;
void text1(int a,int b) {int t;t = a;a = b;b = t;
}//值传递
void text2(int*a,int*b) {assert(a);assert(b);int t;t = *a;*a = *b;*b = t;
}//址传递
void text3(int& a,int& b) {//int& a=a; int& b=b; int t;t = a;a = b;b = t;
}//引用传递
void text4(int* const a,int* const b) {//int* const a=&a; int* const b=&b;int t;t = *a;*a = *b;*b = t;
}
int main() {//引用做函数的形参 可以修改传递过来实参变量的值int a = 5;int b = 10;text1(a, b);//值传递text2(&a,&b);//址传递text3(a,b);//引用传递text4(&a,&b);return 0;
}
优点
指针要判空的,但是引用不用 可以避免很多麻烦
引用做函数的返回值
在内存中不会产生返回值的副本
6.引用与指针的区别
1.引用必须进行初始化,但是指针可以不初始化,为野指针
2.引用不用初始化为空,但是指针可以初始化为空,为空指针
3.引用与变量的关系不能改变,但是指针的指向确可以改变
4.引用所占的空间大小为 类型大小所对应的字节,而指针4/8Byte
#include <iostream> using namespace std; int main() {int a = 10;int& b = a;int* c = &a;cout<<"引用的大小:" << sizeof(b)<<endl<<"指针变量的大小:"<<sizeof(c)<<endl;return 0; }
5.没有多级引用,但是有多级指针,二级三级指针
6.引用的++是指别名所在的那段空间数据的++,而指针的++是指向的移动
7.引用作为函数返回值的好处
可以避免调用拷贝构造函数,节约时间的成本
1.函数默认参数
函数栈
函数调用,是主调函数向被调函数传值,然后被调函数返回结果给主调函数的一个过程
这个过程是需要函数栈来辅助的
栈是向下生长的,就是由高地址向低地址开辟空间
堆是向上生长的,就是由低地址向高地址开辟空间
形参在入栈的时候是从右向左入栈的,而实参传值给形参的时候是从左向右传值(相当于出栈的顺序)
函数的默认参数是在函数调用的过程中发生的,实参在没有传递给形参值之前,形参就已经有了默认参数
格式:返回值类型 函数的名字(形参=默认值){ }
#include <iostream>using namespace std;
int func(int a = 1, int b = 2, int c = 3) {//函数形参的默认参数cout << "你好" << endl;
}
int func1(int a = 1, int b, int c = 3) {cout << "OK" << endl;
}
int main() {func(3, 2, 1);//预期:30 20 10func1(2, 3);return 0;
}
剖析下上面程序的执行过程当函数执行到主函数func位置时,会由函数的调用,主调函数调用被调函数,被调函数会调用一个新的函数栈,被调函数的形参从右向左入栈,一次是c、b、a,然后主调函数向被调函数传值,按照从左到右的顺序传值
注意:如果某个位置有默认值,那么他的后面一般也要有默认值
原因:主函数中你认为值2会传给b但是实际上 他是按从左到右的顺序去传递的 2被赋值给了a b没有被赋值 所以会报错
#include <iostream>using namespace std; int func1(int a = 1, int b, int c = 3) {cout << "OK" << endl; } int main() {func1(2);return 0; }
2.函数的重载
为什么C++有重载但是C语言却没有呐?
原因:C语言函数的组成与C++函数组成有区别
C中函数就是由函数名组成的
C++函数是由函数名+参数组成的
也就是说C++中参数不同但函数名相同的函数是不同的函数,而C语言里只要函数名一样就是相同函数了
所以C语言中不存在在函数的重载
函数重载的作用
函数名可以相同,提高复用性
在调用相同函数名的函数的时候,通过参数列表的不同来却分到底进入哪个函数
参数列表的不同可以是参数个位的不同,参数顺序的不同,参数类型的不同等等
#include <iostream>using namespace std;
void fun(int a, int b) {cout << 1 << endl;
}
void fun(int a, float b) {cout << 2 << endl;
}
void fun(double a) {cout << 3 << endl;
}
void fun(float a) {cout << 4 << endl;
}
int fun(int a, double b) {return 5;
}
//int fun(float a) {
// cout << 5 << endl;
//}//报错了 因为函数的重载就函数名相同参数不同 返回值对函数重载的判断不起作用
void fun(int a, int b, int c) {cout << 5 << endl;
}
int main() {fun(1, 1);fun(1, 1.1f);cout << fun(1, 1.1)<<endl;fun(1.1);fun(1.1f);return 0;
}
当引用作为函数重载的条件
#include <iostream>using namespace std;
void func(int &b) {cout << "1" << endl;
}
void func(const int &b) {cout << "2" << endl;
}
void func(int&& b) {cout << "3" << endl;
}
int main() {int a = 2;func(a);func(2);return 0;
}
引用分为左值引用 右值引用 和 万能引用
可以用这个来作为函数参数的不同
函数重载遇到函数默认参数
观看下面的示例,确实符合函数重载的定义(函数名相同,参数列表不同可以重载),但是仔细观察第一个函数是有一个默认参数的,理论上它传递2个值就可以。而第二个函数就是传递俩个参数,这就会造成编译器无法确定你到底要将这俩个值传递给谁
#include <iostream>using namespace std;
void func(int a,int b,int c=1) {cout << "1" << endl;
}
void func(int a,int b) {cout << "2" << endl;
}
int main() {func(1,2);return 0;
}
一个函数是否能够重载与它的返回值无关
#include <iostream>using namespace std;
void func(int a, int b) {cout << "1"<<endl;
}
int func(int a, int b) {cout << "2" << endl;
}
int main() {func(1, 1);return 0;
}
编译器无法仅根据返回值的类型来判断是否能够重载
函数的重载是一个静态的多态,它先编译阶段就能够判断运行的是哪个函数
3.递归
递归的模板(4件套)
1.结束条件
2.前进
3.递归的调用
4.回退
int func(){if();//递归的结束条件//前进func();//递归的调用//回退
}
斐波那契数列的递归
为什么斐波那契数列能用递归
能写出递归树
比如你想算第五项的斐波那契数列
你要想求5 就必须得知道4和3 你要想知道4和3 就必须得知道2和1 你要想知道 2和 1 就必须知道1 和 0 而1 和0 我们还真知道 所以符合递归
递归树如图:
int Fb(int n) {if (n == 0) return 0;if (n == 1) return 1;int ret=Fb(n-1)+Fb(n-2);return ret;
}
int main() {Fb(5);return 0;
}
爬楼梯的递归
int Tj(int n) {if (n == 1) return 1;if (n == 2) return 2;int ret=Tj(n-1)+Tj(n-2);return ret;
}
int main() {Tj(5);return 0;
}
递归树如图:
# 1.由struct类过度到类
C中的struct类与C++中struct的区别
我们在C语言中都学过struct类型 他是一种复合类型 里面可以定义各种基本类型的变量 但是不知道你是否留意 struct类里面不能定义函数
例子1:
#include <stdio.h>
struct Student {int a;int b;void show() {//非法的}
};
int main() {return 0;
}
#include <iostream>using namespace std;struct Student {int a;int b;void show() {//合法的}
};
int main() {return 0;
}
通过上面的这个例子我们可以看出,
struct类型在C中不可以定义函数,但是在C++中可以定义函数
在看一下第二个例子
例子2
#include <stdio.h>struct Student {int a;int b;
};
int main() {struct Student s1;//合法的Student s1;//非法的return 0;
}
#include <iostream>using namespace std;struct Student {int a;int b;void show() {//合法的}
};
int main() {struct Student s1;//合法的Student s1;//合法的return 0;
}
通过例子2我们可以看出,在C中如果不用typedef关键字,我们定义结构体变量只能用struct Student,使用Student是不合法的,而在C++中这俩种方式都是合法的
在C中结构体名称不可以定义变量,但是C++中结构体名称可以定义变量
结合这俩点我们不难看出 C++对结构体类型进行了某种程度的升级
“某种程度”–>函数的升级,以及定义“变量”的升级,都更加方便了
这种升级就是把struct升级成了C++语言中的类
这就是我们今天的主题,在这里就引出了
2.类
可以把类看成就C中struct类型的plus版
结构体和类的唯一区别就是默认访问权限和继承权限
C++面向对象的三大特点
1.封装
2.继承
3.多态
!!C++中认为万事万物皆为对象,对象有它的属性和行为
属性–>成员变量
行为–>成员函数
封装
格式:class 类名 { 访问权限:属性、行为};
不要忘了分号!!!
int a=10;
和int一个a等于10一样,必须加分号
意义:讲属性和行为放在一起,并加以权限控制
C语言太自由了主函数中向修改结构体类型里面变量的值就修改
C++把它们封装了起来,加了访问权限的控制
访问权限:公有的、受保护的和私有的
1.public
2.protected
3.private
C++中类下默认访问权限是private
struct类的默认访问权限是public,因为要兼容C中的struct类
#include<iostream>
using namespace std;
struct Student {//struct类里面没有说明什么权限 默认权限是public//class类里面没有说明什么权限 默认权值是privatestring _name;string _num;void Set_name(string name) {_name = name;}void Set_num(string num) {_num = num;}void show() {cout << _name << endl << _num << endl;}
};
int main() {Student s1;//合法的s1._name = "123";//合法的默认是publicreturn 0;
}
#include<iostream>
using namespace std;
class Student {//struct类里面没有说明什么权限 默认权限是private//class类里面没有说明什么权限 默认权值是classstring _name;string _num;void Set_name(string name) {_name = name;}void Set_num(string num) {_num = num;}void show() {cout << _name << endl << _num << endl;}
};
int main() {Student s1;//合法的s1._name = "123";//非法的 默认位private 访问不到return 0;
}
类的作用域
public–>共有的作用域为 类内函数、子类和对象
protected–>受保护的作用域为 类内函数、子类
private–>私有的作用域 类内函数
this关键字
先看个例子
#include<iostream>
using namespace std;
class Student {//struct类里面没有说明什么权限 默认权限是private//class类里面没有说明什么权限 默认权值是class
private:string _name;string _num;
public:void Set_name(string name) {_name = name;}void Set_num(string num) {_num = num;}void show() {cout << _name << endl << _num << endl;}
};
int main() {Student s1;//合法的Student s2;s1.Set_name("syc");//语句1s2.Set_name("lh");//语句2return 0;
}
语句1要执行的是把字符串syc赋值给s1的_name
语句2要执行的是把字符串lh赋值给s2的_name
this指针里面存的就是s1和s2的地址
this关键字是指向当前对象的指针,谁调用这个函数,this指针就指向谁
本质:this指针的本质是在实参传值给形参的过程中,形参多了一个指针常量this用来接收调用这个函数对象的地址
#include<iostream>
using namespace std;
class Student {
private:string _name;string _num;
public:void Set_name(Student* const this1,string name) {//指针常量 指向方向不变this1->_name = name;}void Set_num(Student* const this2,string num) {this2->_num = num;}void show() {cout << _name << endl << _num << endl;}
};
int main() {Student s1;//合法的Student s2;s1.Set_name(&s1,"syc");s2.Set_name(&s2,"lh");return 0;
}
特点:其实this指针的特性在上一个模块(this指针的引出)已经讲解的差不多了,这里简要说明:
1.this指针的类型:指针常量指向调用这个函数的对象
2.只能在“成员函数”的内部使用
3.this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所4.以对象中不存储this指针。
5.this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递此处为摘抄。
类的对齐和结构体的对齐很相似
注意:计算类的大小不包括成员函数,只计算成员变量
1.构造函数
什么是构造函数?
构造函数是在创建对象的时候对成员变量进行赋值的一个函数
<注意>
构造函数是对成员变量进行赋值,而不是初始化
因为是先有了对象同时对象里面的成员变量被实例化了,之后对象调用构造函数,对成员变量进行赋值
构造函数的格式
类名+(){ }
观察这个格式我们可以看出它相对于普通函数的相同点和不同点
相同点:它与普通函数一样都有函数名、参数列表、函数体
不同点:它没有返回值 并且它强制要求函数名必须是类名
把类名作为函数名可以很明显的区分出来 这个是构造函数 方便你找到它
无参默认构造函数
解释:如果在类里面没有自己定义构造函数的话,编译器会自动提供一个无参的默认构造函数
<注意>:如果定义了构造函数,那么编译器不会提供默认构造函数
构造函数的分类及调用
分类
按参数分:有参构造、无参构造
按类型分:普通构造、拷贝构造
//构造函数的分类
Pointer() {//无参构造cout << "调用无参构造" << endl;
}
//explicit
Pointer(int* _p1) {//禁止隐式调用的有参构造 cout << "调用有参构造 再堆区申请空间" << endl;
}
Pointer(int a) {//有参构造p1 = new int[a];cout << "调用有参构造" << endl;
}
构造函数的调用
1.括号调用(常用)
2.显示调用
3.隐式调用
如何禁止隐式调用–>使用explicit关键字
int main() {//构造函数调用时候的分类int a=2;int* pt = &a;Pointer s1(pt); //括号调用Pointer s2 = Pointer(pt);//显示调用Pointer s3=pt; //隐式调用-->只能作用于需要传递1个参数的构造函数//explicit 关键字 禁止隐式调用 Pointer* s4 = new Pointer(a);delete s4;return 0;
}
Pointer(int a) {//有参构造p1 = new int[a];cout << "调用有参构造" << endl; } Pointer* s4 = new Pointer(a);
这代码的内存分区图如下
构造函数的具体实现代码
#include <iostream>
#include <cmath>
using namespace std;
class Pointer {
public:int* p1;int p2;//模拟的默认构造函数 当类内没有构造函数的时候 编译器会自己生成一个默认无参构造函数Pointer() {cout << "无参构造函数" << endl;}//有参构造函数Pointer(int n) {if (n) {p1 = new int[n];cout << "使用有参构造函数再堆区中开辟了" << n << "个int这么大小的空间 对象的成员变量p1指向这段空间" << endl;}elsecout << "输入的n不合法" << endl;}//构造函数的重载 第二个函数与第三个函数 函数名相同但是参数列表不同符合函数重载Pointer(int a, int b) {p2 = a + b;cout << "构造函数有重载!" << endl;}//判断函数是否能够重载的时候要关注形参是否有默认参数//Pointer(int a, int b, int c = 1) {// cout << "";//}//符合函数重载的条件,但是由于默认参数的出现,编译器懵逼了 都2个参数 不知道调用哪个了
};
int main() {Pointer s1;//调用默认无参构造Pointer s2();//看着也像是一个无参默认构造函数 但是编译器会把它识别成一个函数声明Pointer s3(5);//调用有参构造Pointer s4(1, 4);//构造函数也有重载 出现了默认参数//Pointer s5(1, 3, 5);//构造函数也有重载return 0;
}
注意
构造函数的重载
构造函数也像普通函数一样具有重载,及函数名都是相同的类名,参数列表不同,符合函数重载
但是要注意的和普通函数一样,当出现默认参数的时候,要仔细观察它是否符合函数重载
看下面这段代码:
Pointer() {cout << "无参构造函数" << endl;}
Pointer s2();//错误 看着也像是一个无参默认构造函数 但是编译器会把它识别成一个函数声明
Pointer s2;//正确
看着是不是很想是一个无参构造函数,但是实际上编译器很认为他是一个函数的声明;
在调用默认构造函数的时候不用加括号!!!!否则会被编译器认为是函数的声明!!!!!!!
2.析构函数
什么是析构函数?
用于释放成员变量指向的堆区空间
什么时候调用析构函数?
当对象要被销毁的前 编译器自动调用析构函数
析构函数的格式
~类名(){ }
与构造函数比 多了一个~表示析构函数
析构函数不存在重载,它参数列表参数为空
~Pointer() {cout << "调用了析构函数" << endl;
}
Pointer* s5= new Pointer();
delete s5;
delete先调用析构函数,释放掉成员变量所指向的堆区空间,然后调用free函数把对象所在的堆区空间释放
3.new与malloc的区别
从返回值上来看
new返回的地址是自动转换的
malloc返回的地址是需要强转的
从名称上
new是运算符,可以调用重载运算符函数(operator)进行重载
而malloc函数是C语言的库函数,C语言没有函数的重载,所以malloc没有函数重载
参数上来看
new是不需要传参的,它分配空间的大小由编译器根据类型计算得出
而malloc是需要传参的,传递的是具体开辟空间的大小的字节数
从底层在来看
new是先调用malloc函数,先在堆区中开辟,如果这段空间的类型是类的话,会调用构造函数,对对象里面的成员变量进行赋值
而malloc仅仅只是在堆区中开辟空间
从空间开辟失败的后果上来看
new开辟空间失败会抛出一段异常
而malloc会返回一个空指针
从已分配内存不够用扩张上来看
new不支持内存的扩张
malloc可以调用realloc扩张内存
4.delete与free的区别
1.从参数上来看–>delete不需要传递参数,free需要传参
2.从底层上来看
delete关键字会先调用析构函数,再调用free函数
具体过程如下:delete函数会先调用析构函数释对象中成员变量所指向的堆区空间,然后再调用free函数释放对象所在的堆区空间
而free函数,不会调用析构函数,只释放对象所在的堆区空间
问题1:为什么delete的底层不是先调用free函数再调用析构函数那??????
Pointer() {cout << "无参构造函数" << endl;}
Pointer s2();//错误 看着也像是一个无参默认构造函数 但是编译器会把它识别成一个函数声明
Pointer s2;//正确
看这段代码,它的内存分区图如下
如果我们先调用的是free函数,那么对象所在的堆区空间会先被释放,那么成员变量如果有指向其他堆区空间的话,这段空间将会再也无法被找到了,会造成内存的泄露
但是如果先调用析构函数,先把成员变量指向的堆区空间释放了,就不会造成这个问题
!!!!故delete函数是先调用析构,再调用free
问题2:new可以和free一起搭配吗?
new正常搭配的是delete–>free+析构
所以当成员变量没在堆区申请空间的时候,也就是析构函数不起作用的时候 delete可以替换free 但是别给自己找麻烦还是别这么用了!
0.初始化列表是在构造函数中使用的
1.初始化列表的格式
初始化列表与构造函数紧密相关的
构造函数参数列表的后面加上:成员变量1(参数列表的值),成员变量2(参数列表的值)…
A(int a, int b, int c) :_b(b),_a(_b), _c(c) {//初始化列表cout << _a << " " << _b << " " << _c << endl;
}
2.初始化列表的作用?
1.给父类继承过来的成员变量进行初始化
当父类没有无参构造时,需要通过父类名调用父类的构造函数
2.给本类的成员变量进行初始化
非类成员,通过成员名(值)初始化
类成员,通过对象调用构造函数
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{int a1;int a2;
public:A(int _a1,int _a2){a1=_a1;a2=_a2;}
};
class B:public A{int b1;A a3;
public:B(int _a1,int _a2,int _b1,int _a31,int _a32)://初始化列表-->1.给继承过来的父类的成员变量初始化,必须通过父类名显示调用// 2.给本类下的成员变量进行初始化,类成员通过对象名括号调用构造函数A(_a1,_a2),a3(_a31,_a32){b1=_b1;}
};
void text2_01(){B b(1,2,3,4,5);//调用无参构造函数不用加括号
}
3.为什么要使用初始化列表?
原因1:const类型的成员变量和引用类型的成员变量必须初始化,而构造函数是在创建对象的时候对成员变量进行赋值,不是初始化,这样在编译的时候就会报错
原因2:类成员,比如说在类A里面定义类B的成员,如果类B有构造函数的话,系统将不会提供默认无参构造函数,那么如果不用初始化列表的话就会报错。
原因3:使用初始化列表会比正常赋值高效一些
4.始化列表特点
a.只能在构造函数中使用
因为对象的初始化只发生在对象创建的时候,而对象创建的时候会调用构造函数
构造函数的执行又俩个阶段
1.初始化阶段:无论成员变量是否在初始化列表中,都会进行初始化
2.赋值阶段:根据函数体里面内容进行赋值
b.成员变量初始化的顺序只于它在类中声明的顺序有关,与初始化列表的顺序无关
int _a;
int _b;
int _c;
A(int a, int b, int c) :_b(b),_a(_b), _c(c) {//初始化列表cout << _a << " " << _b << " " << _c << endl;
}
A(1, 2, 3);
c.const常量、引用和类成员必须在初始化列表中初始化
因为他们三必须创建的时候就进行初始化!!!
首先const类型的变量和引用类型都必须进行初始化操作,否则会报错
d.类对象做类成员
B中有A类成员,要先创建B中的成员变量,调用B的构造函数,然后再对B中的成员变量进行赋值
先调用类成员的构造函数,再调用本类的构造函数,最后先调用本类的析构函数,再调用类成员的析构函数
#include <iostream>using namespace std;
class A {int a;int b;int c;
public:A(int _a, int _b, int _c) {a = _a;b = _b;c = _c;cout << "调用A的构造函数" << endl;}~A() {cout << "调用A的析构函数" << endl;}
};
class B {int x1;int x2;const int cn;int& m;A s;
public:B(int _x1,int _x2,int _cn, int _m, int _a, int _b, int _c) :cn(_cn), m(_m), s(_a, _b, _c) {x1 = _x1;x2 = _x2;cout << "调用B的构造函数" << endl;}~B() {cout << "调用B的析构函数" << endl;}
};int main() {int x = 1;B b(6,7,1, x, 2, 3, 4);return 0;
}
1.静态成员变量
格式:类内->在成员变量的类型前面加一个static
静态成员变量只能初始化一次
它存储在静态区,静态成员变量不占用对象的空间,在编译阶段分配内存,也就是主函数之前
静态成员变量属于类不属于对象,多个对象共享一个共有的静态成员变量
必须在类内定义,类外初始化
不能调用构造函数初始化,因为构造函数归类所有,不是对象所有
可以通过对象名或者类名直接访问静态成员变量
//对象名.静态成员变量
//类名::静态成员变量
静态成员变量在子类和父类共享静态成员变量
在主函数之前执行的有什么?
静态成员变量的初始化和全局变量的初始化
#include<iostream>using namespace std;
class B {
public:B() {cout << "B的构造函数" << endl;}
};
class A {int a;
public:static B m;//静态的类成员static int n;static void Show() {cout << "hello" << endl;}
};
int A::n = 0;//类外初始化
B A::m=B();
/*
* 1.静态成员变量属于类,不属于对象,多个对象共享一个静态成员变量
* 2.静态成员变量在类内定义,类外初始化
* 3.可以通过对象名或类名直接访问公有的静态成员变量
* 4.静态成员变量在编译阶段分配内存,也就是主函数之间
* 5.主函数之前会执行什么?
* 静态成员变量的初始化和全局变量的初始化
*/
int main() {cout << "__________________________" << endl;A a1,a2,a3;a1.n++;a2.n++;a3.n++;A::n++;cout << A::n << endl;cout << a1.n << endl;cout << a2.n << endl;cout << a3.n << endl;return 0;
}
2.静态成员函数
成员函数存储在代码段内,不占用对象空间
静态成员函数属于类,不属于对象
可以通过类名和对象名去调用公有的静态成员函数
静态成员函数没有this指针,只能访问静态成员
因为没有this指针的话,编译器就无法确定究竟是对哪个对象的非静态成员变量进行操作
成员函数不占用对象的空间,成员函数是统一放在代码段当中的,当对象想访问它的成员函数的时候,会到代码段中去寻找那段代码。成员函数通过this指针去区分究竟对哪个对象里面的数据进行操作
#include <iostream>using namespace std;
class A {int b;static int a;
public:static void func1() {}static void func() {this->b = 1;func1();a = 2;}
};
int A::a = 0;int main() {return 0;
}
3.在C++中空指针是可以访问成员函数的
#include <iostream>using namespace std;class A {int a = 2;
public:void func1() {a = 1;cout << "1" << endl;}static void func2() {cout << "2" << endl;}
};
int main() {A* a = nullptr;return 0;a->func1();a->func2();A::func2();
}
但是要注意有没有用到this指针,因为空指针调用成员函数的时候this指针也为空,对象里面是没有内容的。
为了保持程序的健壮性,可以对成员函数进行判空操作
if(this==NULL) return ;
4.静态成员函数与常函数中this指针的区别
静态成员函数:没有this指针,无法访问对象,所以也不能访问对象里面的非静态成员变量
常函数:有this指针,但this指针的类型是一个常量指针常量,无法修改对象里面成员变量的值
5.为什么要有this指针?
—————————————————————————————————————————————————————
6.空类的大小为1字节
因为有类型的内存叫类,所以要是类里面啥也没有就给它一个1字节大小
1.常对象
格式:const Type a;//常对象
性质:
1.因为有const,所以常对象里面的成员变量的值不能修改
2.常对象只能调用常函数
3.非常对象优先调用非常函数,若无非常函数,再调用常函数
2.常函数
格式:在成员函数参数列表的后面加上一个const
void get_nums() const {this->a = 2;
}
性质:
1.常函数有this指针,但是类型是常指常类型,无法修改对象里面的成员变量的值,也不能调用非常函数
2.常函数与非常函数会发生函数重载
如果有俩个函数,它的函数名和参数均相同,什么情况下会发生函数重载?
在这俩个函数一个是常函数,一个是非常函数的时候会发生函数重载,因为编译器会根据对象是否是常对象,来调用不同的函数。
3.成员变量在声明的时候前面加mutable,就可以在常函数中修改其值
4.常函数只能再类中定义,出了类定义是错误的
5.常函数不能调用非常函数,因为常函数的this指针为常指常,而非常函数的this指针为指常,类型不兼容
#include <iostream>using namespace std;
class Person {mutable int a;char b;
public:Person() {cout << "构造函数" << endl;}~Person() {cout << "析构函数" << endl;}void get_nums() {this->a = 2;}void get_nums() const {this->a = 2;}void get_nums2() {//非常函数//this指针是一个指针常量-->Type* const this(指向不能改变,但是可以修改里面的内容)a = 2;}void get_nums2cst() const {//常函数//this指针是一个常量指针常量-->const Type* const this (指针的指向和指向对象里面的内容都不可以改变)a = 2;//常函数,不能修改成员变量的值}
};
void show() const {//常函数只能再类中定义
//错误!
}
int main() {Person p1;//非常对象const Person p2;//常对象-->对象里面的成员变量的值不可以修改p1.get_nums2();//非常对象调用非常函数p1.get_nums2cst();//非常对象调用常函数p2.get_nums2cst();//常对象调用常函数p2.get_nums2();//常对象不可以调用非常函数/** 总结:常对象只能调用常函数,非常对象优先调用非常函数,没有非常函数再调用常函数*/p1.get_nums();p2.get_nums();//函数的重载,编译器可以通过调用对象的不同来区分调用常函数还是非常函数return 0;
<注意>:如果函数名一样,参数列表也一样,也有可能发生函数重载,因为有常函数和非常函数,编译器通过常对象和非常对象来区分
3.常函数、非常函数与静态成员函数中this指针的区别
在非常函数中:this指针是一个指针常量,指向不可以改变
在常函数中:this指针是一个常量指针常量,指向和指向里面的内容均不可以改变,这也就是为什么常函数里面不可以修改成员的是属性。
静态成员函数中:根本没有this指针,所以无法修改对象里面成员变量的值
1.为什么要有友元?
一个类外函数(全局函数)或者类想访问另一个类私有的成员属性的时候,就要用到友元
关键词:friend
2.使用友元的三种情况
1.全局函数做友元
告诉我想访问的那个类,我是你们这个类的我的好朋友,可以让我访问私有成员属性
#include <iostream>using namespace std;
class Building {friend void GoodFreind(Building& b);string bedroom;//私有的
public:string sittingroom;Building();//类内声明构造函数/*{this->bedroom = "卧室";this->sittingroom = "客厅";}*/
};
Building::Building() {this->bedroom = "卧室";this->sittingroom = "客厅";
}
void GoodFreind(Building& b) {//当全局函数想访问一个类中的私有成员属性的时候,把这个函数声明成友元函数就可以实现了cout << "朋友相进入" << b.sittingroom << endl;cout << "朋友想进入" << b.bedroom << endl;
}int main() {Building b;GoodFreind(b);return 0;
}
2.类做友元
友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。
当一个函数是另外一个类的友元函数的时候,那么它就可以访问另一个类的私有成员变量
#include <iostream>using namespace std;class Building {friend Friend;//Friend类设为友元string bedroom;//私有的
public:string sittingroom;Building();//类内声明构造函数
};
class Friend {Building *b;
public:Friend();//类内声明构造函数~Friend();void vist();7
};
Building::Building() {this->bedroom = "卧室";this->sittingroom = "客厅";
}
Friend::Friend(){b = new Building();//成员变量指向堆区空间了,要调用析构函数
}
Friend::~Friend() {if(b) delete b;
}
void Friend::vist() {//一个类想访问另一个类的私有成员,把这个类变为友元cout << "朋友想进入" << b->sittingroom;cout << "朋友想进入" << b->bedroom;
}
int main() {Building b;return 0;
}
3.类中的成员函数做友元
#include <iostream>using namespace std;
class Friend {Building* b;
public:Friend();//类内声明构造函数~Friend();void vist();
};class Building {friend void Friend::vist();//Friend类里面的成员函数设为友元string bedroom;//私有的
public:string sittingroom;Building();//类内声明构造函数
};
class A{
public:void visit();
};
Building::Building() {this->bedroom = "卧室";this->sittingroom = "客厅";
}
Friend::Friend(){b = new Building();//成员变量指向堆区空间了,要调用析构函数
}
Friend::~Friend() {if(b) delete b;
}
void Friend::vist() {//一个类想访问另一个类的私有成员,把这个类变为友元cout << "朋友想进入" << b->sittingroom;cout << "朋友想进入" << b->bedroom;
}
int main() {Building b;return 0;
}
3.友元的好坏
好处:可以访问类中的私有成员
坏处:破坏了封装性
访问一个类的私有成员变量由俩种方法
1.对象调用get set函数
2.把一个类和全局函数设置为友元
运算符的重载有俩种形式,一种类内(成员函数)重载运算符,一种类外(全局函数)重载运算符,类内可以少传递一个参数,但是不是所有运算符重载都可以再类内中进行的,比如<< >>
1.重载’+'运算符
值返回
为什么要重载加法运算符?
因为C++提供的加法运算符只能满足基本数据类型间的加法,如果我想让俩个相同的类的对象进行加法的话会报错
所以为了能让俩个相同类的对象进行加法,我们要把这个过程封装到一个函数里面,只不过要多加一个关键字operator而已,让编译器一下子就找到,这个是重载运算符的函数
作用:实现俩个自定义运算符相加
成员函数实现运算符重载
可以少传递一个参数
#include <iostream>using namespace std;class Box {int length;int width;int height;
public:Box() {length = 0;width = 0;height = 0;}Box(int length,int width,int height) {this->length = length;this->width = width;this->height = height;}Box(const Box& other) {length = other.length;width = other.width;height = other.height;}//成员函数重载加法运算符Box operator+(const Box& other) {Box p;p.length = this->length + other.length;p.width = this->width + other.width; p.height = this->height + other.height;return p;}
};
int main() {Box a(1,2,3);Box b(2, 3, 4);Box c = a + b;//直接调用Box d;d = a.operator+(b);//调用重载运算符的函数return 0;
}
全局函数实现运算符的重载
#include <iostream>using namespace std;class Box {int length;int width;int height;friend Box operator+(const Box& other1, const Box& other2);friend Box operator+(const Box& other1, int val);
public:Box() {length = 0;width = 0;height = 0;}Box(int length,int width,int height) {this->length = length;this->width = width;this->height = height;}Box(const Box& other) {length = other.length;width = other.width;height = other.height;}
};
//全局函数重载加法运算符
Box operator+(const Box& other1,const Box& other2) {//不调用成员函数是无法访问私有的成员变量的,需要设置为友元,告诉编译器,我这个重载运算符的函数是你这个类的好朋友,都哥们,能fBox p;p.length = other1.length + other2.length;p.width = other1.width + other2.width;p.height = other1.height + other2.height;return p;
}
Box operator+(const Box& other1,int val) {Box p;p.length = other1.length + val;p.width = other1.width + val;p.height = other1.height + val;return p;
}
int main() {Box a(1,2,3);Box b(2, 3, 4);Box c = a + b;Box d;d=operator+(a,b);return 0;
}
2.重载’+=’ 运算符
引用返回
#include <iostream>using namespace std;
class Box {int length;int width;int high;friend Box& operator+=(Box& other1, Box& other2);
public:Box() {length = 1;width = 2;high = 3;}/*Box& operator+=(const Box& other) {this->length += other.length;this->width += other.width;this->high += other.high;return *this;}*/int get_length() {return this->length;}
};
Box& operator+=(Box& other1,const Box& other2) {other1.length += other2.length;other1.width += other2.width;other1.high += other2.high;return other1;
}
int main() {Box a, b,c;a += b+=c;//隐式调用函数cout << a.get_length();return 0;
}
3.重载’<<'运算符
引用返回
cout是ostream类的对象
cin是istream类的对象
#include <iostream>using namespace std;
class Box {int length;int width;int high;friend ostream& operator<<(ostream& o, const Box& b);
public:Box() {length = 1;width = 2;high = 3;}
};
ostream& operator<<(ostream& o,const Box& b) {o << b.length << ' ' << b.width << ' ' << b.high << endl;return o;
}
int main() {Box a,b,c;//cout << a;没有与这些操作数相匹配的运算符/** 你想重载一个运算符要么在类内重载,要么在类外重载* 但是cout对象属于ostream类,该类我们无法修改,所以只能在类外用全局函数重载*/cout << a << b << c;return 0;
}
4.重载’>>'运算符
引用返回
#include <iostream>using namespace std;
class Box {int length;int width;int high;friend ostream& operator<<(ostream& o, const Box& b);friend istream& operator>>(istream& i, Box& b);
public:Box() {length = 1;width = 2;high = 3;}
};
ostream& operator<<(ostream& o,const Box& b) {o << b.length << ' ' << b.width << ' ' << b.high << endl;return o;
}
istream& operator>>(istream& i,Box& b) {i >> b.length;i >> b.width;i >> b.high;return i;
}
int main() {Box a, b,c;//cout << a;没有与这些操作数相匹配的运算符/** 你想重载一个运算符要么在类内重载,要么在类外重载* 但是cout对象属于ostream类,该类我们无法修改,所以只能在类外用全局函数重载*/cin >> a>>b>>c;cout << a << b << c;return 0;
}
5.重载’++'运算符
前加加–>引用返回
后加加–>值返回
#include <iostream>
using namespace std;
class A {//private 类内 int a;int b;int c;friend ostream& operator<<(ostream& o, const A& other);
public:A() {this->a = 0;this->b = 0;this->c = 0;}A& operator++() {//前加加a++;return *this;}A operator++(int) {//后加加//不要返回局部变量的引用A t = *this;a++;return t;}
};
//
ostream& operator<<(ostream& o, const A& other) {o << other.a << endl;return o;
}
int main() {A s;cout << s++;cout << ++s;return 0;
}
6.重载’比较’运算符
#include <iostream>
#include <iostream>
using namespace std;class Person {string name;int age;
public:Person(string name, int age) {this->name = name;this->age = age;}bool operator==(const Person& other) const{if (this->age == other.age) {return 1;}return 0;}bool operator>(const Person& other) const{if (this->age > other.age) {return 1;}return 0;}bool operator<(const Person& other) const{if (this->age < other.age) {return 1;}return 0;}bool operator!=(const Person& other) const{if (this->age != other.age) {return 1;}return 0;}
};
int main() {Person p0("施易辰", 20);Person p1("田雪嵩", 100);if (p0 == p1) {cout << "年龄相等" << endl;}else if (p0 > p1) {cout << "p0大于p1" << endl;}else if (p0 < p1) {cout << "p0小于p1" << endl;}elsecout << "p0不等于p1" << endl;return 0;
}
7.重载’='赋值运算符
引用返回
编译器自动提供的赋值运算符是浅拷贝类型,如果成员变量指向堆区空间的话,会连续俩次析构函数会报错
#include <iostream>
#include <iostream>
using namespace std;class Person {string name;int age;int* password;friend ostream& operator<<(ostream& o, const Person& p);
public:Person(string name, int age, int mm) {this->name = name;this->age = age;this->password = new int[10];*password = mm;}~Person() {if (password) delete[]password;}
};
ostream& operator<<(ostream& o, const Person& p) {o << p.name << ' ' << p.age << *(p.password) << endl;return o;
}
int main() {Person p0("施易辰", 20, 12345);Person p1("田雪嵩", 100, 123425);p0 = p1;//编译器自动提供的是浅拷贝类型的赋值运算符 成员变量指向堆区空间时候会报错cout << p0 << p1;return 0;
}
所以如果成员变量指向堆区空间的话要自己重载赋值运算符,类型为深拷贝模式
#include <iostream>
#include <iostream>
using namespace std;class Person {string name;int age;int* password;friend ostream& operator<<(ostream& o, const Person& p);
public:Person(string name, int age, int mm) {this->name = name;this->age = age;this->password = new int[10];*password = mm;}~Person() {if (password) delete[]password;}Person& operator=(const Person& other) {//深拷贝模式的赋值运算符this->name = other.name;this->age = other.age;this->password = new int[10];*password = *(other.password);return *this;}
};
ostream& operator<<(ostream& o, const Person& p) {o << p.name << ' ' << p.age << *(p.password) << endl;return o;
}
int main() {Person p0("施易辰", 20, 12345);Person p1("田雪嵩", 100, 123425);p0 = p1;//调用自定义的赋值运算符的重载函数cout << p0 << p1;return 0;
}
不会报错!
8.重载’()'运算符
又称仿函数
#include <iostream>using namespace std;class Myprint {
public:void operator()(string text) {cout << text << endl;}
};
class Myadd {
public:int operator()(int a, int b) {return a + b;}
};
void text01() {Myprint myprint;myprint("abc");myprint.operator()("dce");
}
void text02() {int a = 1;int b = 2;Myadd myadd;cout << myadd.operator()(1, 2) << endl;cout << myadd(3, 4) << endl;
}
int main() {text01();text02();return 0;
}
1.继承的基本语法
class 类名:继承权限 父类{
}
class A{int a;int b;int c;
};
class B:public A{//在子类中,abc已经被继承过来了,不用写重复的代码
};
A被称为父类、基类
B被称为子类、派生类
继承的作用:可以减少重复的代码
2.继承权限
和访问权限一样,同样有三种
第一种private继承权限
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
private:int pri;void prifun();
protected:int pro;void profun();
public:int pub;void pubfun();
};
class son:private father{//继承权限作用:缩小父类成员在子类中的访问权限,只会缩小不会扩大/** 私有的继承权限,缩小了子类的访问权限* 成员变量pro和成员函数profun都变成私有的了,在grandson中可以看出* 成员变量pub和成员函数pubfun都变成私有的了,*/void fun(){pri=1;//错误prifun();//错误,父类中私有的成员子类都不可以访问pro=1;//父类中受保护的和共有的成员子类可以访问profun();pub=1;pubfun();}};
class grandson:public son{void fun(){pro=1;//错误profun();//错误pub=1;//错误pubfun();//错误}
};
void text01(){grandson s;s.pub=1;//错误
}
第二种protect继承权限
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
private:int pri;void prifun();
protected:int pro;void profun();
public:int pub;void pubfun();
};
class son:protected father{//继承权限作用:缩小父类成员在子类中的访问权限,只会缩小不会扩大void fun(){pri=1;//错误prifun();//错误,父类中私有的成员子类都不可以访问pro=1;//父类中受保护的和共有的成员子类可以访问profun();pub=1;pubfun();}};
class grandson:public son{void fun(){pro=1;profun();pub=1;pubfun();}
};
void text01(){grandson s;s.pub=1;//错误
}
第三种public继承权限
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
private:int pri;void prifun();
protected:int pro;void profun();
public:int pub;void pubfun();
};
class son:public father{//继承权限作用:缩小父类成员在子类中的访问权限,只会缩小不会扩大/** 私有的继承权限,缩小了子类的访问权限* 成员变量pro和成员函数profun都变成私有的了,在grandson中可以看出* 成员变量pub和成员函数pubfun都变成私有的了,*/void fun(){pri=1;//错误prifun();//错误,父类中私有的成员子类都不可以访问pro=1;//父类中受保护的和共有的成员子类可以访问profun();pub=1;pubfun();}};
class grandson:public son{void fun(){pro=1;profun();pub=1;pubfun();}
};
void text01(){grandson s;s.pub=1;//可以
}
通过上面三段代码我们可以看出
父类中如果有private类型的成员,无论以何种继承方式,都无法在子类中访问
虽然是无法访问,但是它仍旧被继承了过来,只是被隐藏了,仍然占用子类的空间
3.继承中的构造析构顺序
构造函数
当创建子类对象的时候,会优先调用父类的构造函数,然后调用子类的构造函数
为什么?
因为子类对象需要先继承父类的成员变量,也就是先创建父类的成员变量,然后要对父类对象赋值,所以调用父类构造函数,然后再创建自己的成员变量,调用自己的构造函数
析构函数
入栈出栈顺序有关
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
public:father(){cout<<"father构造"<<endl;}~father(){cout<<"father析构"<<endl;}
};
class son:public father{
public:son(){cout<<"son构造"<<endl;}~son(){cout<<"son析构"<<endl;}
};
class grandson:public son{
public:grandson(){cout<<"grandson构造"<<endl;}~grandson(){cout<<"grandson析构"<<endl;}
};
void text01(){grandson gr;
}
4.继承同名函数的处理
使用作用域类区别不同类下的成员函数
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
public:father(){cout<<"father构造"<<endl;}void fun(){cout<<"father的fun函数"<<endl;}
};
class son:public father{
public:son(){cout<<"son构造"<<endl;}void fun(){cout<<"son的fun函数"<<endl;}
};
class grandson:public son{
public:grandson(){cout<<"grandson构造"<<endl;}void fun(){cout<<"grandson的fun函数"<<endl;}
};
void text01(){father f;son s;grandson gr;f.father::fun();s.son::fun();gr.grandson::fun();}
5.继承同名静态成员变量的处理
静态成员变量俩种调用方式,作用域和对象名调用
但是无论是哪种调用,再同名静态成员变量继承中,都要加上作用域
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
public:static int a;
};
int father::a=1;class son:public father{
public:static int a;
};
int son::a=2;class grandson:public son{
public:static int a;
};
int grandson::a=3;
void text01(){grandson gr;cout<<gr.father::a<<endl;//对象调用cout<<gr.son::a<<endl;cout<<gr.a<<endl;cout<<father::a<<endl;//作用域调用cout<<son::a<<endl;cout<<grandson::a<<endl;
}
6.多继承
多继承构造函数的调用顺序与继承的顺序有关,谁写再前面先继承谁
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{
public:int a;A(){cout<<"A构造"<<endl;}
};class B{
public:int a;B(){cout<<"B构造"<<endl;}
};class C:public B,public A{
public:C(){cout<<"C构造"<<endl;}
};
void text01(){C c;c.B::a;//多继承问题中会存在二义性-->解决方式,作用域c.A::a;
}
7.菱形继承
同样存在二义性但是与多击沉不同可以通过virtual来解决,也可以通过作用域解决
#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{
public:int a;A(){cout<<"A构造"<<endl;}
};class B:virtual public A{
public:B(){cout<<"B构造"<<endl;}
};class C:virtual public A{
public:C(){cout<<"C构造"<<endl;}
};
class D:public B,public C{
public:D(){cout<<"D构造"<<endl;}
};
void text01(){D d;d.B::a;d.a;
}