C++基础学习
基础语法(一些)
常量
记录程序种不可更改的数据
1、#define宏常量:文件上方定义,表示一个常量
2、const修饰的变量:变量定义前关键字,表示常量,不可修改
const int clo = 11;
数据类型
数据类型 | 占用空间 |
---|---|
整型 | |
short(短整型) | 2字节 |
int(整型) | 4字节 |
long(长整型) | windows为4字节,Linux为4字节(32位),8字节(64位) |
long long(长长整型) | 8字节 |
浮点型 | |
float(单精度) | 4字节(7位有效数字) |
double(双精度) | 8字节(15~16位有效数字) |
字符型 | |
char | 1字节(将对应ASCII码存入存储单元) |
字符串型 | 规范 |
C语言风格 | char 变量名[] = “字符串值”; |
C++风格 | string 变量名[] = “字符串值”; |
布尔类型 | |
true(1)/false(0) | 1字节 |
float pai = 3.14f;//注意结尾f,未添加将识别为double类型
默认情况下输出一个小数只会显示6位有效数字
科学计数法:
float f1 = 3e2;//3*10^2
float f2 = 3e-2;//3*0.1^2
sizeof:统计数据类型所占内存大小,单位:字节
sizeof(数据类型/变量)
数据输出:cout
cout << 数据
数据输入:cin
cin >> 变量
逻辑运算符
! //非
&& //与
|| //或
选择结构与循环结构
三目运算符
返回值为变量,可以继续赋值
//语法
表达式1 ? 表达式2 : 表达式3
表达式1为真==》执行表达式2并返回表达式2,否则执行表达式3并返回表达式3
例:a,b做比较,将最大的值赋值给c
int a = 1;
int b = 2;
int c;
c = (a > b ? a : b);
switch注意点:switch表达式类型只能为整型或字符型
随机数
例:1~100
#include<iostream>
using namespace std;
#include<ctime>
int main()
{
srand((unsigned int)time(NULL));
int num = rand() % 100 + 1;
cout << "随机数: " << num << endl;
system("pause");
return 0;
}
break:用于跳出选择结构或者循环结构
使用场景:
1、switch中,终止case并跳出switch
2、循环语句中,跳出当前的循环语句
3、嵌套循环中,跳出最近的内层循环语句
continue:在循环语句中,跳过本次循环中剩下未执行的语句,继续进行下一次循环
goto:无条件跳转语句
语法:
goto 标记;
//若标记名称存在,当执行到goto语句时,则会跳转至标记的位置
数组
数组:存在一段连续的内存,使用相同的数据类型,定义数组必须有初始长度
*一维数组名可以直接打印数组的首地址
int arr[] = {1,2,3,4,5};
cout << arr << endl; //将输出16进制的内存地址
cout << &arr[0] << endl; //将输出数组第一个元素的内存地址 &:取地址
二维数组
数据类型 数组名[行数][列数];
数据类型 数组名[行数][列数] = {{数据1, 数据2} , {数据3, 数据4}}; 可读性更强
数据类型 数组名[行数][列数] = {数据1,数据2,数据3,数据4};
数据类型 数组名[][列数] = {数据1,数据2,数据3,数据4};
函数
1、返回值类型,若不需要返回值==》void
2、函数名
3、参数表列
4、函数体语句
5、return表达式
语法:
返回值类型 函数名 (参数列表){
函数体语句
return 表达式
}
函数声明:提前告诉编译器该函数存在;
若函数定义位于调用函数之后,且函数未声明,将无法执行;
声明可以有多次,定义只能有一次;
若函数声明中存在默认参数,则在函数实现中不能有默认参数;
且在函数声明和函数实现中只能有一个有默认参数。
占位参数:
语法:返回值类型 函数名(数据类型){}
占位参数也可以有默认参数,例 int = 10;
void test(int){
cout << "hello world" << endl;
}
函数的分文件编写流程
1、创建.h的头文件
2、创建.cpp的源文件
3、在头文件中写函数声明
4、在源文件中写函数定义
函数重载
==》提高复用性
适用范围:
1、在同一个作用域下
2、函数名称相同
3、函数参数类型不同或者个数不同或者顺序不同
且:函数的返回值不能作为函数重载的条件
注意事项
//1、引用作为重载条件
void test(int &a){
cout << "1" << endl;
}
void test(const int &a){ //类型不同
cout << "2" << endl;
}
int main(){
int a=10;
test(a); //将调用第一个未重载前的函数
//int &a = a合法
test(10);//将调用重载后的函数
//因为int &a = 10不合法
//const int &a = 10合法
}
//2、函数重载与默认参数
void test(int a,int b = 10){
cout << "1" << endl;
}
void test(int a){
cout << "1" << endl;
}
int main(){
test(10); //出现二义性,报错
}
指针
//定义指针
int a = 10;
int * p = &a; //32位操作系统4字节,64操作系统8字节
cout << &a << endl; //a的地址
cout << p << endl; //a的地址
cout << &p << endl; //p的地址,p中存放a的地址
//使用指针
*p = 1000; //解引用
cout << a << endl; //a被修改为1000
cout << *p << endl; //1000
cout << p << endl; //p中存放着a的地址
空指针无法被访问(0~255的内存编号是系统占用的,所以不可访问)
野指针:指针变量指向非法的内存空间(非申请空间)
int * p = (int *)0x1100;//地址空间并未申请
cout << *p << endl;
const修饰指针:
1、const修饰指针:常量指针,指针的指向可以修改,指针指向的值不能修改
int a = 10;
const int * p = &a;
2、const修饰常量:指针常量,指针的指向不可修改,指针指向的值可以修改
int a = 10;
int * const p = &a;
3、const既修饰指针,又修饰常量,指针的指向和指向的值都不能修改
int a = 10;
const int * const p = &a;
指针与数组
int arr[] = {1,2,3,4,5};
cout << arr[0] << endl;
int * p = arr; //数组首地址
cout << *p << endl;
p++; //让指针向后偏移4个字节到达下一个元素地址
cout << *p << endl;
指针与函数
利用指针作为函数参数,可以修改实参的值
void swaqnum(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
swaqnum(&a, &b); //地址传递
cout << "a: " << a << endl;
cout << "b: " << b << endl;
system("pause");
return 0;
}
冒泡排序
#include<iostream>
using namespace std;
//冒泡排序
void bubblezsSort(int* arr, int arrlen) {
for (int i = 0; i < arrlen; i++)
{
for (int j = 0; j < arrlen-i-1; j++)
{
if (arr[j] > arr[j + 1]) {
int temp = arr[j]; //临时
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//打印数组
void printarray(int* arr, int arraylen) {
for (int i = 0; i < arraylen; i++)
{
cout << arr[i] << endl;
}
}
int main()
{
int arr[] = { 3,5,6,7,2,44,13 };
//数组长度
int arrlen = sizeof(arr) / sizeof(arr[0]);
bubblezsSort(arr, arrlen);//排序 arr为数组首地址
printarray(arr, arrlen);//输出
system("pause");
return 0;
}
结构体
用户自定义的数据类型,允许用户存储不同的数据类型
//创建结构体
strcut 结构体名{
//结构体成员列表
//例:
string name;
int age;
};
//创建结构体变量
//1、
struct 结构体名 变量名; //struct关键字可以省略
变量名.name = "张三"; //访问结构体变量中的属性
变量名.age = 18;
//2、
struct 结构体名 变量名 = {"张三" , 18}; //struct关键字可以省略
//3、在创建结构体时顺便创建结构体变量
strcut 结构体名{
//结构体成员列表
//例:
string name;
int age;
}变量名;
//后续可以直接访问结构体变量
//结构体数组
struct 结构体名 数组名[] = {
{"张三",18},
{"李四",20}
};
数组名[下标].属性 = 值;//修改赋值
结构体指针:利用指针访问结构体中的成员
利用操作符 -> 可以通过结构体指针访问结构体属性
例:
struct sssss
{
string name;
int age;
};
sssss s = {"张三", 18}; //创建结构体变量
sssss* p = &s; //创建结构体指针
cout << p->name << endl; //通过结构体指针访问结构体成员变量
cout << p->age << endl;
结构体嵌套
struct 子结构体名{
//成员变量
};
struct 结构体名{
//其他成员变量
//嵌套子结构体
struct 子结构体名 子结构体变量;
}
//访问
父结构体变量.子结构体变量.子结构体属性
结构体作为函数参数
//1、值传递
void 函数名(struct 结构体名 结构体形参){
cout << 结构体形参.成员变量 << endl;
};
//2、地址传递
//采用地址传递方式将可以减少内存空间的消耗
void 函数名(struct 结构体名 * 结构体指针){
cout << 结构体指针->成员变量 << endl;
};
//const修饰
void 函数名(const struct 结构体名 * 结构体指针){
结构体指针->成员变量 = 值;//不允许写操作,将报错
cout << 结构体指针->成员变量 << endl; //允许读操作
};
小例子
通讯录管理系统纯代码实现
#include<iostream>
using namespace std;
#define MAX 1000 //通讯录最大人数
//联系人结构体
struct Person {
string name;
int sex; //1:男 2:女
int age;
string phone;
string addree;
};
//通讯录结构体
struct Addressbooks {
Person personarray[MAX]; //联系人数组
int P_Size; //人员个数
};
//菜单
void showMenu() {
cout << "1、添加联系人" << endl;
cout << "2、显示联系人" << endl;
cout << "3、删除联系人" << endl;
cout << "4、查找联系人" << endl;
cout << "5、修改联系人" << endl;
cout << "6、清空联系人" << endl;
cout << "0、退出程序" << endl;
cout << "请输入你的选项: " << endl;
}
//添加联系人
void addPerson(Addressbooks * books) {
//判断通讯录是否已满
if (books->P_Size == MAX) {
cout << "通讯录已满" << endl;
return;
}
else
{
//添加联系人信息
//姓名
string name;
cout << "请输入姓名: " << endl;
cin >> name;
books->personarray[books->P_Size].name = name;
//性别
int sex;
while (true)
{
cout << "请输入姓别: " << endl;
cout << "1 男" << endl;
cout << "2 女" << endl;
cin >> sex;
if (sex == 1 || sex == 2)
{
books->personarray[books->P_Size].sex = sex;
break;
}
cout << "输入的姓别选项有误 " << endl;
}
//年龄
int age;
cout << "请输入年龄: " << endl;
cin >> age;
books->personarray[books->P_Size].age = age;
//电话
string phone;
cout << "请输入联系方式: " << endl;
cin >> phone;
books->personarray[books->P_Size].phone = phone;
//住址
string address;
cout << "请输入住址: " << endl;
cin >> address;
books->personarray[books->P_Size].addree = address;
//更新通讯录人数
books->P_Size++;
cout << "联系人添加成功" << endl;
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
}
}
//显示联系人
void printPerson(Addressbooks* books) {
if (books->P_Size == 0) {
cout << "当前的记录为空" << endl;
}
else
{
for (int i = 0; i < books->P_Size; i++)
{
cout << i + 1 << "、" << "姓名:" << books->personarray[i].name << "\t";
//三目运算
//cout << "性别:" << (books->personarray[i].sex == 1?"男":"女") << "\t";
if (books->personarray[i].sex == 1)
{
cout << "姓别:男" << "\t";
}
else
{
cout << "姓别:女" << "\t";
}
cout << "年龄:" << books->personarray[i].age << "\t";
cout << "电话:" << books->personarray[i].phone << "\t";
cout << "住址:" << books->personarray[i].addree << endl;
}
}
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
}
//监测联系人是否存在
int isExist(Addressbooks* books, string name) {
for (int i = 0; i < books->P_Size; i++)
{
if (books->personarray[i].name == name) {
return i; //找到返回这个人在数组中的下标
}
}
return -1; //未找到,返回-1
}
//删除联系人
void deletePerson(Addressbooks* books) {
cout << "请输入将要删除的联系人的姓名:" << endl;
string name; //联系人姓名
cin >> name;
int ret = isExist(books, name);
if (ret != -1)
{
//找到此人
//数据前移,覆盖要删除的联系人
for (int i = ret; i < books->P_Size; i++)
{
books->personarray[i] = books->personarray[i + 1];
}
books->P_Size--;//更新人员数
cout << "删除成功" << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
}
//查找联系人
void findPerson(Addressbooks* books) {
cout << "请输入要查找的联系人姓名:" << endl;
string name; //联系人姓名
cin >> name;
int ret = isExist(books, name);
if (ret != -1)
{
//找到此人
cout << ret + 1 << "、" << "姓名:" << books->personarray[ret].name << "\t";
cout << "性别:" << (books->personarray[ret].sex == 1 ? "男" : "女") << "\t";
cout << "年龄:" << books->personarray[ret].age << "\t";
cout << "电话:" << books->personarray[ret].phone << "\t";
cout << "住址:" << books->personarray[ret].addree << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
}
//修改联系人
void editPerson(Addressbooks* books) {
cout << "请输入要修改的联系人姓名:" << endl;
string name; //联系人姓名
cin >> name;
int ret = isExist(books, name);
if (ret != -1)
{
//找到此人
//姓名
cout << "开始修改:" << endl;
cout << "请输入姓名: " << endl;
cin >> name;
books->personarray[ret].name = name;
//性别
int sex;
while (true)
{
cout << "请输入姓别: " << endl;
cout << "1 男" << endl;
cout << "2 女" << endl;
cin >> sex;
if (sex == 1 || sex == 2)
{
books->personarray[ret].sex = sex;
break;
}
cout << "输入的姓别选项有误 " << endl;
}
//年龄
int age;
cout << "请输入年龄: " << endl;
cin >> age;
books->personarray[ret].age = age;
//电话
string phone;
cout << "请输入联系方式: " << endl;
cin >> phone;
books->personarray[ret].phone = phone;
//住址
string address;
cout << "请输入住址: " << endl;
cin >> address;
books->personarray[ret].addree = address;
cout << "联系人修改成功……" << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
}
//清空通讯录
void cleanPerson(Addressbooks* books) {
int change;
while (true)
{
cout << "请确认是否要清空通讯录" << endl;
cout << "1 确认" << endl;
cout << "0 取消" << endl;
cin >> change; //选项
if (change == 1)
{
books->P_Size = 0;
cout << "通讯录已清空……" << endl;
break;
}
else if (change == 0) {
//取消清空
//退出清空功能
cout << "退出清空联系人功能……" << endl;
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
return;
}
cout << "输入的选项不正确" << endl;
}
system("pause");//暂停进程,按任意键继续
system("cls");//清屏
}
int main()
{
int select = 0;//用户选项变量
Addressbooks books; //通讯录结构体变量
books.P_Size = 0; //初始化人员个数0
while (true)
{
showMenu(); //菜单
cin >> select; //输入选项
switch (select)
{
case 1: //1、添加联系人
addPerson(&books); //地址传递
break;
case 2: //2、显示联系人(所有)
printPerson(&books);
break;
case 3: //3、删除联系人
deletePerson(&books);
break;
case 4: //4、查找联系人
findPerson(&books);
break;
case 5: //5、修改联系人
editPerson(&books);
break;
case 6: //6、清空联系人
cleanPerson(&books);
break;
case 0: //0、退出程序
cout << "欢迎下次使用" << endl;
system("pause");
return 0;
default:
cout << "输入的选项有误,请重新输入" << endl;
break;
}
}
system("pause"); //暂停进程
return 0;
}
内存模型
内存分区模型==》不同的区域存放不同数据,给予不同的生命周期,实现灵活编程
1、代码区:存放函数体的二进制代码,由操作系统进行管理
2、全局区:存放全局变量和静态变量以及常量
3、栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不能返回局部变量的地址
int * func(){
int a = 10; //局部变量
return &a;
}
int main() {
int * p = func(); //接受返回值
cout << *p << endl; //第一次可以成功打印==》因为编译器做了保留
cout << *p << endl; //第二次数据将不再保留
return 0;
}
4、堆区:由程序员分配和释放,若程序员不释放,在程序结束时由操作系统回收;在C++中通常使用new在堆区开辟内存,使用delete释放内存
//new返回的是该数据类型的指针
int * p = new int(10); //开辟
int * arr = new int[10]; //创建数组
//释放
delete p;
delete[] arr; //释放数组
引用:给变量起别名,对引用的操作都会转为对引用对象的操作
数据类型 &别名 = 原名
引用必须初始化;引用一旦初始化后,将不允许更改
引用必须引一块合法的内存空间
int a = 10;
int &b = a; //引用初始化
int c = 20;
b = c; //此处进行的是赋值操作,不是更改引用
//执行后,a=20,b=20,c=20
引用作函数参数==》可以简化指针修改实参
//引用传递
void func(int &a,int &b){
……………………;
}
int main(){
……………………;
int a =10;
int b =20;
func(a,b); //引用传递,形参会修饰实参
}
引用做返回值类型
//不能返回局部变量引用
//函数的调用可以作为左值
int& test(){
static int a = 10; //静态变量,存放于全局区
return a;
}
int main(){
int &ret = test(); //引用
test() = 100; //对原名赋值
cout << ret2 << endl; //别名将输出100
}
引用本质:在c++内部实现一个指针常量
int a = 10;
int& ret = a; //自动转换为int* const ret = &a; 指针常量=》指向不可改==》说明引用不可改
ret = 20; //内部发现ret是引用==》自动转换为 *ret = 20;
常量引用:修饰形参
const int & ret = 10;
//经过const修饰后,编译器将代码修改为:
//且只读,不可修改
int temp = 10; //合法临时空间
int& ret = temp;
void test(const int &val){
val = 1000;//不合法操作,不允许修改
//不使用const修饰将可以修改,并且实参也将被修改
}
类和对象
三大特性:封装,继承,多态
封装
将属性与行为作为一个整体,将属性与行为加以权限控制
语法:class 类名{ 访问权限: 属性 / 行为 };
#include<iostream>
using namespace std;
const double pie = 3.14;
class Circle
{
//访问权限
//公共权限
public:
//属性
//半径
int m_r;
//行为
//获取圆的周长
double ZC() {
return 2 * pie * m_r;
}
};
int main() {
//通过圆类,创建圆对象
Circle c1;
//对圆对象赋值
c1.m_r = 10;
cout << "圆的周长:" << c1.ZC() << endl;
system("pause");
return 0;
}
权限:
1、pubic 公共权限,类内可以访问,类外也可以访问
2、protected 保护权限,类内可以访问,类外不可访问 儿子可以访问父亲中的保护内容
3、private 私有权限,类内可以访问,类外不可访问
struct与class的区别:
1、struct默认权限为公共
2、class默认权限为私有
成员属性设置私有:
1、将所有成员属性设置为私有,可以自己控制读写权限
2、对于写权限,可以检测数据的有效性
例:点与圆位置关系
#include<iostream>
using namespace std;
class Point
{
public:
//获取x,y坐标
int getX() {
return m_x;
}
int gety() {
return m_y;
}
//设置x,y坐标
void setXY(int x,int y) {
m_x = x;
m_y = y;
}
private:
int m_x;
int m_y;
};
class Circle
{
public:
//设置半径
void setR(int r) {
m_r = r;
}
//获取半径
int getR() {
return m_r;
}
//设置圆心
void setcenter(Point center) {
m_center = center;
}
//获取圆心
Point getcenter() {
return m_center;
}
private:
int m_r;
Point m_center; //在类中可以让另外一个类作为本类中的成员
};
//判断点和圆的关系
void isInCircle(Circle &c, Point &p){
//计算两点之间的距离平方
int distance =
pow(c.getcenter().getX() - p.getX(), 2) +
pow(c.getcenter().gety() - p.gety(), 2);
//计算半径的平方
int rdistance = pow(c.getR(), 2);
//判断关系
if (distance == rdistance)
{
cout << "点在圆上" << endl;
}
else if(distance < rdistance) {
cout << "点在圆内" << endl;
}
else
{
cout << "点在圆外" << endl;
}
}
int main() {
//创建圆
Circle c;
c.setR(3);
//圆心
Point center;
center.setXY(0, 0);
c.setcenter(center);
//创建点
Point p;
p.setXY(0, 2);
//判断关系
isInCircle(c, p);
system("pause");
return 0;
}
对象的初始化和清理
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
没有返回值也不写void;
函数名称与类名相同;
构造函数可以有参数,所以可以发生重载;
程序在调用对象时自动调用构造函数,无需手动调用,并且只会调用一次
析构函数语法:~类名(){}
没有返回值也不写void;
函数名称与类名相同,在名称前加上~;
析构函数不能有参数,因此不能发生重载;
程序在对象销毁时自动调用析构,无需手动调用,并且只能调用一次
构造函数的分类以及调用
分类:
1、按照参数分为:有参构造和无参构造
2、按照类型分为:普通构造和拷贝构造
调用:
1、括号法
2、显示法
3、隐式转换法
#include<iostream>
using namespace std;
class person
{
public:
person() {
cout << "无参构造函数" << endl;
}
person(int a) {
cout << "有参构造函数" << endl;
}
person(const person& p) {
cout << "拷贝构造函数" << endl;
}
~person() {
cout << "析构函数" << endl;
}
private:
};
void text() {
//1、括号法
person p1; //默认的构造函数调用;调用时不要使用();
//若使用person p1();编译器将会将其认为是一个函数声明
person p2(20); //有参构造函数
person p3(p1); //拷贝构造函数
//2、显示法
person p4; //默认构造
person p5 = person(10); //有参构造
person p6 = person(p5); //拷贝构造
//匿名对象:
person(10);
cout << "执行结束即释放" << endl;
//特点:当前执行结束后,系统会立即回收匿名对象
//不能使用拷贝构造函数 初始化匿名对象
//person(p3);
//编译器会认为:person (p3) === person p3 ===>名称重定义
//3、隐式转换法
person p7 = 10;//相当于 person p7 = person(10);
person p8 = p7;//拷贝构造
}
int main() {
text();
system("pause");
return 0;
}
拷贝构造函数
使用场景:
使用一个已经创建完毕的对象来初始化一个新对象;
值传递的方式给函数参数传值;
以值方式返回局部对象。
//值传递
void test01(person p){
}
void test02(){
person p;
test01(p); //值传递
}
//以值方式返回局部对象
person test03(){
person p1;
return p1;
}
person test04(){
person p = test03();
}
构造函数调用规则
1、如果用户定义有参构造函数,C++不在提供默认的无参构造,但是会提供默认拷贝构造
2、如果用户自定义了拷贝构造函数,C++将不在提供其他构造函数
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区申请空间,进行拷贝操作
注意:若属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题(重复释放同一块内存)
初始化列表:初始化属性
语法:构造函数():属性1(值1),属性2(值2) …… {}
#include<iostream>
using namespace std;
class person
{
public:
person(int a,int b,int c):m_a(a),m_b(b),m_c(c){}
private:
int m_a;
int m_b;
int m_c;
};
int main() {
person p(1, 2, 3);
system("pause");
return 0;
}
类对象作为类成员
class A{}
class B{
A a; //优先构造
}
构造的时候先构造类对象,再构造自身,析构的顺序与构造相反
静态成员
在成员变量和成员函数前+关键字static,称为静态成员
静态成员分为:
1、静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
2、静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
class person
{
public:
static int m_a; //类内声明
private:
};
int person::m_a = 100; //类外初始化
int main() {
person p1;
person p2;
p2.m_a = 200;
cout << p1.m_a << endl; //200 共享内存
cout << person::m_a << endl; //通过类名进行访问
system("pause");
return 0;
}
对象模型与this指针
类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象
空对象占用空间:1字节==》为了区分空对象占内存的位置
class person
{
public:
private:
int m_a; //非静态成员变量 属于类对象上
static int m_b; //静态成员变量 不属于类对象上
void func1(){} //非静态成员函数 不属于类对象上
static void func2() {} //静态成员函数 不属于类对象上
};
int main() {
person p;
cout << sizeof(p) << endl; //4
system("pause");
return 0;
}
this指针:指向被调用的成员函数 所属的对象;
this指针是隐含每一个非静态成员函数内的一种指针;
this指针不需要定义,直接使用即可。
this指针使用范围:
1、当形参和成员变量同名时,可以使用this指针区分
2、在类的非静态成员函数中返回对象本身,可使用return *this;
#include<iostream>
using namespace std;
class person
{
public:
person(int age) {
this->age = age;
}
person& personaddage(person& p) {
this->age += p.age;
//this是指向p2的指针
//*this指向p2对象本体
return *this;
}
int age;
private:
};
int main() {
person p1(10);
person p2(10);
//链式编程
p2.personaddage(p1).personaddage(p1);
cout << p2.age << endl; //30
system("pause");
return 0;
}
空指针访问成员函数
#include<iostream>
using namespace std;
class person
{
public:
void printperson() {
cout << "this is person" << endl;
}
void showperson() {
//若没有判断this指针将会报错
//原因为传入的指针为NULL
if (this == NULL) {
return;
}
cout << "age = " << this->age << endl;
}
private:
int age;
};
int main() {
person* p = NULL;
p->printperson();
p->showperson();
system("pause");
return 0;
}
const修饰成员函数
常函数:
1、成员函数后加上const称这个函数为常函数
2、常函数内不可以修改成员属性
3、成员属性声明时加上关键字mutable后,在常函数中依然可以修改
常对象:
1、声明对象前加上const称该对象为常对象
2、常对象只能调用常函数
class person
{
public:
//this指针的本质:指针常量 指针的指向不能修改
//person * const this
//const person * const this 指向的值也不能改
void func() const{
this->m_a = 100; //报错,不能修改
this->m_b = 100; //合法
this = NULL;//报错,不能修改指针的指向
}
void showperson() {
m_a = 100;
cout << "show person" << endl;
}
int m_a;
mutable int m_b; //特殊变量,即使在常函数中,也可以修改
private:
};
void test() {
const person p; //常对象
p.m_a = 100; //报错,不可修改
p.m_b = 100; //合法
//常对象只能调用常函数
p.func(); //合法
p.showperson() //报错
}
友元
使一个函数或者类,访问另外一个类中私有成员
关键字:friend
实现:
1、全局函数做友元
class person
{
//friend关键字
//test将能访问person类中的私有成员
friend void test(person* p); //全局函数做友元
public:
person() {
m_a = 20;
m_b = 30;
}
int m_a;
private:
int m_b; //私有
};
//全局函数
void test(person* p) {
cout << p->m_a << endl;
cout << p->m_b << endl; //访问私有成员
}
void func() {
person p;
test(&p);
}
2、类做友元
class person
{
friend class test; //友元
public:
person();
int m_a;
private:
int m_b; //私有
};
//类外实现成员函数
person::person() {
m_a = 20;
m_b = 30;
}
class test
{
public:
test();
void func(); //用于访问person中的属性
person* p;
private:
};
//类外实现成员函数
test::test()
{
p = new person; //在堆区创建对象
}
void test::func() {
cout << p->m_a << endl; //访问共有属性
cout << p->m_b << endl; //访问私有属性
}
void showperson() {
test tt;
tt.func();
}
3、成员函数做友元
class person;
class test
{
public:
test();
void func(); //用于访问person中的属性
person* p;
private:
};
//类外实现成员函数
test::test()
{
p = new person; //在堆区创建对象
}
void test::func() {
cout << p->m_a << endl; //访问共有属性
cout << p->m_b << endl; //访问私有属性
}
class person
{
friend void test::func(); //友元
public:
person();
int m_a;
private:
int m_b; //私有
};
//类外实现成员函数
person::person() {
m_a = 20;
m_b = 30;
}
void showperson() {
test tt;
tt.func();
}
运算符重载
运算符重载也能实现函数重载
注意:
1、对于内置的数据类型的表达式的运算符是不可改变的
2、不能滥用运算符重载
加号:实现两个自定义数据类型相加
class person
{
public:
//成员函数重载+号
//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;
//}
int m_a;
int m_b;
private:
};
//全局函数重载+号
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 test() {
person p1;
p1.m_a = 10;
p1.m_b = 20;
person p2;
p2.m_a = 30;
p2.m_b = 40;
person p3 = p1 + p2;
cout << p3.m_a << "," << p3.m_b << endl;
}
左移运算符:可以输出自定义数据类型;不能使用成员函数重载,只能使用全局函数重载
左移运算符配合友元可以实现输出自定义数据类
//成员函数重载 无法实现cout在左侧
void operator<<(cout){}
//全局函数重载
//本质:operator<<(cout , p) 简化 cout << p
//若要访问类中的私有属性==》使用友元即可
//friend ostream & operator<<(ostream& cout,person& p);
ostream & operator<<(ostream& cout,person& p) {
cout << p.m_a << p.m_b;
return cout;
}
void test() {
person p1;
p1.m_a = 10;
p1.m_b = 20;
cout << p1 << endl;
}
递增运算符:实现自定义数据类型自增
class person
{
friend ostream& operator<<(ostream& c, person p);
public:
person();
//前置递增
person& operator++() {
m_num++;
return *this;
}
//后置递增
//int :占位参数,用于区分前置和后置递增
person& operator++(int) {
person temp = *this;
m_num++;
return temp; //先返回值,后递增
}
private:
int m_num;
};
person::person()
{
m_num = 0;
}
//重载左移
ostream& operator<<(ostream& c, person p) {
cout << p.m_num;
return c;
}
void test() {
cout << "前置递增" << endl;
person p;
cout << p << endl;
cout << ++(++p) << endl;
cout << p << endl;
}
void test01() {
cout << "后置递增" << endl;
person p;
cout << p << endl;
cout << p++ << endl;
}
赋值运算符重载:
C++编译器至少会给一个类添加4个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行值拷贝
4、赋值运算符operator=,对属性进行值拷贝
若类中有属性指向堆区,做赋值操作的时候将会出现深浅拷贝的问题
class person
{
public:
person(int m_b);
~person();
person& operator=(person &p) {
//编译器提供了浅拷贝
//m_a = p.m_a;
//实现深拷贝
//判断是否在堆区
if (m_a != NULL)
{
delete m_a; //先释放
m_a = NULL;
}
//再深拷贝
m_a = new int(*p.m_a);
return *this;
}
int *m_a;
private:
};
person::person(int m_b)
{
m_a = new int(m_b);
}
person::~person()
{
if (m_a != NULL)
{
delete m_a;
m_a = NULL;
}
}
void test() {
person p1(20);
cout << *p1.m_a << endl;
person p2(40);
cout << *p2.m_a << endl;
p2 = p1;
cout << *p2.m_a << endl;
person p3(70);
cout << *p3.m_a << endl;
p3 = p2 = p1;
cout << *p3.m_a << endl;
}
关系运算符重载:相等,不相等,大于,小于
class person
{
public:
person(string a,int b);
~person();
//相等重载
bool operator==(person& p) {
if (this->m_a == p.m_a && this->m_b == p.m_b) {
return true;
}
return false;
}
//不相等重载
bool operator!=(person& p) {
if (this->m_a != p.m_a || this->m_b != p.m_b) {
return true;
}
return false;
}
string m_a;
int m_b;
private:
};
person::person(string a, int b)
{
m_a = a;
m_b = b;
}
person::~person()
{
}
void test() {
person p1("closure", 20);
person p2("closure", 20);
if (p1 == p2)
{
cout << "p1 == p2" << endl;
}
person p3("closure", 20);
person p4("closure", 21);
if (p3 != p4)
{
cout << "p3 != p4" << endl;
}
}
函数调用运算符重载:
函数调用运算符()也可以重载;由于重载后使用的方法非常像函数的调用==》称为仿函数
仿函数没有固定写法==》灵活
class person
{
public:
void operator()(string text) {
cout << text << endl;
}
};
void test() {
person p;
p("closure"); //类似函数调用==》仿函数
}
继承
语法:
class 子类 : 继承方式 父类
子类也称为派生类;父类也称为基类;
派生类中的成员分为两类,一类是从基类继承,一类是自己增加的成员;
从基类继承过来的表现其共性,新增的成员体现其个性;
继承方式:
1、公共继承
父类公共==》子类公共
父类保护==》子类保护
父类私有==》子类不可访问(编译器隐藏父类私有)
2、保护继承
父类公共==》子类保护
父类保护==》子类保护
父类私有==》子类不可访问(编译器隐藏父类私有)
3、私有继承
父类公共==》子类私有
父类保护==》子类私有
父类私有==》子类不可访问(编译器隐藏父类私有)
注意项:
继承时会将所有非静态成员属性都继承下去,包括私有属性;
继承后创建为子类创建对象将调用父类构造==》子类构造==》子类析构==》父类析构
查看对象模型:
使用VS开发开发人员命令提示符
进入对应项目所在路径,查看类的内存模型:
例:
class person
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
//继承
class son : public person {
public:
int m_d;
};
命令:
cl /d1 reportSingleClassLayout类名 "文件名"
当继承时父类和子类出现了同名成员:
1、子类同名成员,直接访问即可
2、访问父类同名成员,需要加作用域
多继承:
一个类继承多个类
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2……
多继承可能会引发父类中同名成员出现,需要加上作用域以区分
菱形继承:
两个派生类继承同一个基类,又有个类同时继承两个派生类。
class father{
public:
int m_a;
};
class son1 : public father{};
class son2 : public father{};
class grandson : public son1 , public son2{};
void test() {
grandson gg;
//gg.m_a = 100; //报错,不明确
gg.son1::m_a = 100;
gg.son2::m_a = 200;
//菱形继承/多继承,使用作用域区分
//导致数据有两份==》资源浪费
cout << gg.son1::m_a << endl; //100
cout << gg.son2::m_a << endl; //200
//cout << gg.m_a << endl; 报错,不唯一
}
===》解决问题
使用虚继承:在继承方式前加上virtual==》father类就将称为虚基类
class father{
public:
int m_a;
};
class son1 :virtual public father{};
class son2 :virtual public father{};
class grandson : public son1 , public son2{};
void test() {
grandson gg;
//gg.m_a = 100; //报错,不明确
gg.son1::m_a = 100;
gg.son2::m_a = 200;
//菱形继承/多继承,使用作用域区分
//导致数据有两份==》资源浪费
cout << gg.son1::m_a << endl; //200
cout << gg.son2::m_a << endl; //200
cout << gg.m_a << endl; //200
//使用虚继承后数据变得唯一
}
vbptr:虚基类指针,指向虚基类表(vbtable)
通过虚基类指针加上虚基类表中的偏移(8 / 4)即可指向唯一的m_a
多态
静态多态:函数重载和运算符重载属于静态多态(复用函数名)
动态多态:派生类和虚函数实现运行时多态
区别:
1、静态多态的函数地址早绑定==》编译阶段确定函数地址
2、动态多态的函数地址晚绑定==》运行阶段确定函数地址
class fath{
public:
void func() {
cout << "fath" << endl;
}
};
class son : public fath {
public:
void func() {
cout << "son" << endl;
}
};
//地址早绑定,编译阶段确定函数地址
void show(fath& fa) { //fath & fa = s;
fa.func();
}
void test() {
son s;
show(s); //fath
}
//为了实现使用son下的func函数
//使用动态多态实现晚绑定===》将使用虚函数
class fath{
public:
//虚函数
virtual void func() {
cout << "fath" << endl;
}
};
class son : public fath {
public:
void func() {
cout << "son" << endl;
}
};
//地址晚绑定
void show(fath& fa) { //fath & fa = s;
fa.func();
}
void test() {
son s;
show(s); //son
}
动态多态总结:
1、需要满足有继承关系;
2、子类重写了父类的虚函数
在使用时要用父类的指针或者引用去执行子类对象
当父类中的func不是虚函数时
改为虚函数后:
当父类中的func为虚函数,且子类不对其进行重写时:
虚函数表中为&fath::func
当父类中的func为虚函数,且子类对其进行重写时:
虚函数表内原来的&fath::func将被覆盖为&son::func
纯虚函数:
语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚哈函数,这类将被称为抽象类;
抽象类无法实例化对象,且子类必须重写抽象类中的纯虚函数,否则也属于抽象类;
在多态中,通常父类中的虚函数的实现时无意义的==》因此可以将虚函数改为纯虚函数
class person {
public:
virtual void func() = 0; //纯虚函数
};
class son : public person {
public:
//重写
virtual void func() {
cout << "func" << endl;
}
};
void test() {
person* per = new son; //多态,父类指针指向子类对象
per->func();
}
虚析构和纯虚析构:
在使用多态时,若子类中有属性开辟至堆区,那么父类指针在释放时无法调用到子类的析构函数;
===》将父类中的析构函数改为虚析构或者纯虚析构;
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象;都需要具体的函数实现;
区别:
如果是纯虚析构,则该类属于抽象类,无法实例化对象;
语法:
虚析构:virtual ~类名(){}
纯虚析构:virtual ~类名() = 0;
class person {
public:
person() {
cout << "perosn构造" << endl;
}
//虚析构
//virtual ~person() {
// cout << "perosn析构" << endl;
//}
//纯虚析构 此时person类为抽象类,无法实例化对象
//需要声明也需要实现
virtual ~person() = 0;
};
person::~person() {
cout << "perosn纯虚析构" << endl;
}
class son : public person {
public:
son() {
cout << "son构造" << endl;
}
~son() {
cout << "son析构" << endl;
if (m_a != NULL) {
delete m_a;
m_a = NULL;
}
}
void func(int a) {
m_a = new int(a); //堆区
}
int* m_a;
};
void test() {
person* per = new son; //多态,父类指针指向子类对象
delete per;
}
文件操作
程序在运行时产生的数据都属于临时数据,程序一旦运行结束就都会被释放
通过文件操作可以将数据持久化,C++中对文件操作需要包含头文件<fstream>
文件类型:
1、文本文件:文件以文本的ASCII码形式存储在计算机中
2、二进制文件:文件以文本的二进制形式存储于计算机中
操作文件:
1、写:ofstream
2、读:ifstream
3、读写:fstream
文件打开方式:
打开方式 | 描述 |
---|---|
ios::in | 为了读文件而打开文件 |
ios::out | 为了写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 若文件存在则先深处再创建 |
ios::binary | 二进制方式 |
文件打开方式可以配合使用,利用|操作符
例:二进制方式写文件
ios::binary | ios::out
写文件:
//文本文件
#include<iostream>
using namespace std;
#include<fstream>
//写文件
void test() {
ofstream offile; //创建流对象
offile.open("test.txt", ios::out); //文件打开方式
offile << "closure" << endl;
offile << "zh-closure.github.io" << endl; //写内容
offile.close(); //关闭文件
}
//二进制
//主要利用流对象调用成员函数write
//函数原型:
ostream& write(const char * buffer,int len);
//字符指针buffer指向内存中的一段存储空间,len是读写的字节数
class person {
public:
char m_name[64];
int m_age;
};
void test() {
ofstream offile; //创建流对象
offile.open("closure.txt", ios::out | ios::binary);
//写文件
person p = { "closure",20 };
offile.write((const char*)&p, sizeof(person));
offile.close(); //关闭文件
}
读文件:
//文本文件
#include<iostream>
using namespace std;
#include<string>
#include<fstream>
//写文件
void test() {
ifstream offile; //创建流对象
offile.open("test.txt", ios::in); //文件打开方式
//判断文件是否打开成功
if (!offile.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//读数据
//1、
//char buf[1024] = { 0 };
/*while ( offile >> buf )
{
cout << buf << endl;
}*/
//2、
//char buf[1024] = { 0 };
/*while (offile.getline(buf,sizeof(buf)))
{
cout << buf << endl;
}*/
//3、
/*string buf;
while ( getline(offile , buf))
{
cout << buf << endl;
}*/
//4、一个一个读(不推荐使用)
char c;
while ( (c = offile.get()) != EOF ) //EOF end of file
{
cout << c;
}
offile.close(); //关闭文件
}
//二进制
//主要利用流对象调用成员函数read
//函数原型:
istream& read(char *buffer,int len);
//字符指针指向内存中的一段存储空间
class person {
public:
char m_name[64];
int m_age;
};
//写文件
void test() {
ifstream iffile; //创建流对象
iffile.open("closure.txt", ios::in | ios::binary);
//判断文件是否打开
if (!iffile.is_open())
{
cout << "文件打开失败" << endl;
}
//写文件
person p;
iffile.read((char*)&p, sizeof(person));
cout << p.m_name << p.m_age << endl;
iffile.close(); //关闭文件
}