C++核心编程
内存区分模型
c++执行程序的时候,将内存方向大致分为4个区域
1:代码区:存放函数体的二进制代码,由操作系统进行管理的
2:全局区:存放全局变量和静态常量以及常量
3:栈区:由编译器来自动分配释放,存放函数的参数值,局部变量等(由编译器自动管理生死)
4:堆区:有程序员分配和释放,若程序员不释放,程序结束后由操作系统回收
内存四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们最大的灵活编程
程序运行前
程序编译后,生成了exe的可执行的程序,未执行程序前分为两个区域
一:代码区
代码区:存放cpu的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可;
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
二:全局区
全局区:全局变量,静态变量(static修饰的)和常量区(字符串常量和其他常量(const修饰的变量(也叫做全局常量)(语法可参考另外一篇博客c++入门第一阶段——基础篇))
该区域的数据是由程序结束后,由操作系统释放的。
#include<iostream>
using namespace std;
//全局变量
int c = 10;
int d = 10;
const int g = 10;
const int h = 10;
int main()
{
//全局区
//全局变量、静态变量和常量
//创建普通的局部变量
//不在函数体内的变量就是全局变量
int a = 10;
int b = 10;
cout << "局部变量a的地址为:" << (int)&a<<endl;
cout << "局部变量b的地址为:" << (int)&b << endl;
cout << "全局变量c的地址为:" << (int)&c << endl;
cout << "全局变量d的地址为:" << (int)&d << endl;
//静态变量 在普通变量前加上static,属于静态变量
static int e = 10;
static int f = 10;
cout << "静态变量e的地址为:" << (int)&e << endl;
cout << "静态变量f的地址为:" << (int)&f << endl;
//常量分为字符串常量
cout << "字符串常量的地址为;" << (int)&"hellow world" << endl;
//const修饰的变量
//const修饰的变量又分const修饰的全局变量和const修饰的局部变量
cout << "const修饰的全局变量g的地址为;" << (int)&g << endl;
cout << "const修饰的全局变量h的地址为;" << (int)&h << endl;
const int i = 10;
const int j = 10;
cout << "const修饰的局部变量量i的地址为;" << (int)&i<< endl;
cout << "const修饰的局部变量j的地址为;" << (int)&j << endl;
system("pause");
return 0;
}
程序运行后
一:栈区
由编译器自动分配释放,存放函数的参数值,局部变量等;
注意事项:
不要返回局部变量的地址,***栈区开辟的数据***由编译器自动释放的
#include<iostream>
using namespace std;
//全局变量
int c = 10;
int d = 10;
const int g = 10;
const int h = 10;
int func(int b)//形参b也存放在栈区
{
int a = 10;//局部变量存放在栈区,栈区的数据在函数执行完后自动释放
cout<<"b的值为:"<<b<<endl;
b = 100;
return a;//返回局部变量的值
}
int* func1()
{
int a = 100;//局部变量存放在栈区,栈区的数据在函数执行完后自动释放
return &a;//返回局部变量的地址//千万不要返回局部变量的地址
}
int main()
{
int b = func(1);
cout<<b<<endl;
cout<<b<<endl;
cout<<b<<endl;
int *p = func1();
cout<<*p<<endl;//第一次可以打印正确的数据是因为是编译器做了保留
cout<<*p<<endl;//第二次数据不再做保留
system("pause");
return 0;
}
二:堆区
由程序员分配释放,若程序员不释放,程序结束后由操作系统回收
在C++中主要利用new在堆区中开辟内存
#include<iostream>
using namespace std;
int* func()
{
//利用new关键字,可以将数据开辟到堆区
//指针本质也局部变量,放在栈上,指针保存的数据是放在了堆区
int *p=new int(10);
return p;
}
int main()
{
//在堆区开辟数据
int *pp = func();
//首先栈区pp接收到其函数返回值得地址,然后在堆区开辟新的*pp的值
cout<< *pp << endl;
cout<< *pp << endl;
cout<< *pp << endl;
system("pause");
return 0;
}
堆区的数据特点是由管理员开辟和释放
堆区数据是由new关键字进行开辟内存
三:new操作符
c++中利用new操作符在堆区开辟数据
堆区开辟的数据由程序员手动开辟,手动释放,释放利用操作符delete(delete 指针);
语法:new 数据类型
利用new创建的数据,会返回数据对应的类型的指针
数组释放的时候为:delete [ ] 数组名
#include<iostream>
using namespace std;
//1:new的基本语法:
int *func()
{
//在堆区创建整型数据
//new返回的是该数据类型的指针
int *p = new int(10);
return p;
}
void test0()
{
int *p =func();
cout<<*p<<endl;
cout<<*p<<endl;
cout<<*p<<endl;
cout<<*p<<endl;
//堆区的数据由程序员管理开辟,程序员管理释放
delete p;
cout<<*p<<endl;
}
void test02()
{
//创建10个整型数据的数组,在堆区
int *p= new int[10];//10代表的是数组有10个元素
for(int i = 0;i<10;i++)
{
p[i]=i+100;
}
for(int i = 0;i<10;i++)
{
cout<<p[i]<<endl;
}
//释放数组的时候,要加上[]才可以;
delete [] p;//数组已经释放,再读取时为乱码;
for(int i = 0;i<10;i++)
{
cout<<p[i]<<endl;
}
}
int main()
{
// test0();
test02();
system("pause");
return 0;
}
引用
作用:给变量取别名
语法:数据类型 &别名 = 原名
引用的基本使用
#include<iostream>
using namespace std;
int main()
{
// 引用的基本语法
//数据类型 &别名 = 原名;
int a = 10;
int &b = a;
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
b= 100;
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
system("pause");
return 0;
}
引用的注意事项
1:引用必须初始化
2:引用在初始化后,不可改变(自始至终)
#include<iostream>
using namespace std;
int main()
{
// 引用的基本语法
//数据类型 &别名 = 原名;
int a = 10;
//int &b ;//会报错引用变量“b”需要初始值预定项
int &b = a;
int c= 20;
b=c;//这是赋值操作;
//int &b = c;
//错误的:“b”: 重定义;多次初始化
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
system("pause");
return 0;
}
引用做函数参数
作用:
函数传参时,可以利用引用技术让形参修饰实参
优点:可以简化指针修饰实参
#include<iostream>
using namespace std;
//交换函数
//值传递
void mySwap01(int a,int b)
{
int temp = a;
a = b;
b = temp;
cout<<"swap01:a = "<<a<<endl;
cout<<"swap01:b = "<<b<<endl;
}
//地址传递
void mySwap02(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
cout<<"swap02:a = "<<*a<<endl;
cout<<"swap02:b = "<<*b<<endl;
}
//引用传递
void mySwap03(int &a,int &b)
{
int temp = a;
a = b;
b = temp;
cout<<"swap03:a = "<<a<<endl;
cout<<"swap03:b = "<<b<<endl;
}
int main()
{
int a = 10;
int b = 20;
mySwap01(a,b);//值传递,形参不会修饰实参
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
mySwap02(&a,&b);//地址传递,形参会修饰实参的
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
mySwap03(a,b);//引用传递,形参也会修饰实参的
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
system("pause");
return 0;
}
引用做函数的返回值
作用:引用是可以做为函数的返回值的存在
注意:不要返回局部变量引用
用法:函数调用作为左值
#include<iostream>
using namespace std;
//引用做函数的返回值
//1:不要返回局部变量的引用
int& test01()//错误示范:返回局部变量的引用
{
int a = 10;//局部变量存放在内存四区的栈区
return a;
}
//2:函数的调用作为左值
int& test02()
{
static int a = 10;//静态变量放在全局区
return a;
}
int main()
{
int& ref = test01();
cout<<"test01 :ref = "<<ref<<endl;//第一次结果正确:编译器做了保留
cout<<"test01 :ref = "<<ref<<endl;//第二次结果错误:因为a的内存已经释放
int ref2 = test02();
cout<<"test02 :ref2 = "<<ref2<<endl;
cout<<"test02 :ref2 = "<<ref2<<endl;
cout<<"test02 :ref2 = "<<ref2<<endl;
test01() = 1000;
cout<<"test01 :ref = "<<ref<<endl;
cout<<"test01 :ref = "<<ref<<endl;
test02() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值
cout<<"test02 :ref2 = "<<ref2<<endl;
cout<<"test02 :ref2 = "<<ref2<<endl;
cout<<"test02 :ref2 = "<<ref2<<endl;
system("pause");
return 0;
}
引用的本质
本质:引用的本质在c++内部实现是一个指针常量
指针常量:值是可以改变的,指向不可以改变
(可以参考:c++基础)
#include<iostream>
using namespace std;
void func (int& ref)
//发现是引用,转换为int * const ref = a;
{
ref = 1000;//ref是引用,转化为*ref = 100;
cout<<"ref = "<<ref<<endl;
}
int main()
{
int a = 10;
int &b = a;//自动转化为int* const b = &a;//指针常量:值可以改变,指向不可以改变
b = 100;//编译器发现b是引用,自动帮我们转换为:*b = 10;
cout <<"a = " <<a<<endl ;
cout <<"b = " <<b<<endl ;
func(a);
cout<<"a = "<<a;
system("pause");
return 0;
}
引用的本质 就是一个指针常量
引用一旦初始化后,就不可以发生改变
结论:c++推荐用引用技术,因为语法方便,引用本质是指针常量(指向不可以改变,值是可以改变的)所有的指针操作编译器都帮我们做了
常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数参数列表里,可以加const修饰形参,防止形参改变实参
#include<iostream>
using namespace std;
void showvaule (const int& ref)
{
cout<< "ref = "<<ref<<endl;
cout<< "ref = "<<ref<<endl;
}
int main()
{
int a = 10;
//int &ref = 10;//引用的对象必须为合法的内存空间
//int &ref = a; //引用必须引一块合法的内存空间
const int &ref = 10;
//加上const之后,边意思将代码修改为:temp = 10;const int &ref = temp;
//ref = 20;//加入const之后变为只读,不可以修改
int b = 100;
showvaule(a);
cout<< "a = "<<a<<endl;
system("pause");
return 0;
}
函数提高
函数默认参数
在C++中,函数的形参列表里的形参时可以有默认值的
语法:返回值类型 函数名 (参数 = 默认值
)
#include<iostream>
#include"swap.h"
using namespace std;
//函数的默认参数
//注意事项:
//1:如果某个位置已经有了默认参数,那么从这个位置往后,从左往右,都有默认值
//int func(int a, int b =20,int c )//报错:默认的实参不在形参的末尾
int func(int a, int b =10,int c =10 )
//如果我们自己传入了数据,就用自己传入的数据,如果没有,就用默认值
//语法:返回值类型 函数名(形参 = 默认值)
//{
//}
{
return a+b+c;
}
//2:如果函数声明有默认参数,函数实现就不能有默认参数
//声明和实现只能有一个默认参数
int func2(int a = 10, int b = 20 );//函数声明:函数声明是由默认参数的
//int func2 (int a = 100 , int b = 1000 )
//1>h:\c\c++\函数的分文件编写\函数的分文件编写\file of function.cpp(20): error C2572: “func2”: 重定义默认参数 : 参数 2
//1> h:\c\c++\函数的分文件编写\函数的分文件编写\file of function.cpp(18) : 参见“func2”的声明
//1>h:\c\c++\函数的分文件编写\函数的分文件编写\file of function.cpp(20): error C2572: “func2”: 重定义默认参数 : 参数 1
{
}
int main()
{
cout<<func(10,20)<<endl;
system("pause");
return 0;
}
函数占位参数
c++中函数的形参列表里可以有占位参数,用于做占位,调用函数是必须填补该位置
语法:返回值类型 函数名 (数据类型)
{}
在现阶段函数的占位参数存在的意义不大,但在后面的教程中会用到该技术
#include<iostream>
#include"swap.h"
using namespace std;
//占位参数
//返回类型 函数名(数据类型)
//{}
//占位参数,还可以有默认参数
void func(int a,int = 10)
{
cout << "this is func"<<endl ;
}
int main()
{
func(10,20);
system("pause");
return 0;
}
函数重载//chong
作用:函数名可以相同,提高函数的复用性
函数重载满足条件:
1:同一个作用域下
2:函数名相同
3:函数参数类型不同,或者个数不同或者顺序不同
注意:函数的返回值是不可以作为函数重载的条件的
函数重载//chong的注意事项
1:引用作为函数重载的条件
2:函数重载碰到函数的默认参数
#include<iostream>
#include"swap.h"
using namespace std;
//函数重载的注意事项
//1:引用作为函数重载的条件
void func(int &a)//int &a = 10;//不合法的 //引用的对象必须是合法的内存空间
{
cout<<"func(int&a)调用"<<endl;
}
void func(const int &a)
{
cout<<"const int &a调用"<<endl;
}
//2:函数重载碰到默认参数
void func2(int a)
{
cout<<"func2(int a)调用"<<endl;
}
//void func2(int a,int b = 10)//不可以
//{
// cout<<"func2(int a,int b)调用"<<endl;
//}
void func()
{
}
int main()
{
//int a = 10;//a为变量,可读可写
//func(a);
//func(10);
func2(10);//当函数重载碰到默认参数的时候,出现了二义性,报错,尽量避免,不要加默认参数
system("pause");
return 0;
}
类和对象
c++面向对象的三大特性:
封装;继承;多态
c++认为万物都皆为对象,对象上有其属性和行为
例如:
人作为对象:
属性有姓名,年龄,身高,体重。。。。
具有相同性质的对象,我们可以抽象成为类,人属于类
封装
一:
封装的意义一:
将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
#include<iostream>
using namespace std;
//设计一个圆类,求圆的周长
//圆求周长的公式:2:2*π*半径
const double p1 = 3.14;
//class 代表设计一个类,类后面紧接跟着的就是类名称
class Circle
{
//访问权限:
//公共权限
public:
//属性
//半径
int m_r;
//行为
//获取圆的周长
double calculateZc()
{
return 2*p1*m_r;
}
};
int main()
{
//通过圆类 创建具体的圆(对象)
Circle c1;
//实例化(通过类,创建一个对象的过程)
//给圆对象的属性进行赋值
c1.m_r = 10;
cout<< "圆的周长为:" << c1.calculateZc()<<endl;
system("pause");
return 0;
}
作业:
设计一个学生类,属性有姓名和学号,可以给命名和学号赋值,可以显示学生的姓名和学号
#include<iostream>
using namespace std;
//设计一个圆类,求圆的周长
//圆求周长的公式:2:2*π*半径
const double p1 = 3.14;
#include<iostream>
#include<string>
using namespace std;
class student
{
public :
string m_name;
string m_number;
void showNUM()
{
cout <<"学生的名字: "<<m_name<<"\t学生的工号: "<<m_number<<endl;
}
//另外一种表达方式:通过行为给属性赋值
void setName(string name)
{
m_name= name;
}
void setnumber(string number)
{
m_number= number;
}
};
int main()
{
student who;
//cout<<"请输入姓名:"<<endl;
//cin>>who.m_name;
//cout<<"请输入学号:"<<endl;
//cin>>who.m_number;
who.setName("陈莉莉");
who.setnumber("20");
who.showNUM();
system("pause");
return 0;
}
封装的意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制;
1.public :公共权限
2.protected :保护权限
3.private : 私有权限
#include<iostream>
#include<string>
using namespace std;
//公有权限 public: 成员类内可以访问 类外也可以访问
//保护权限 proected:成员类内可以访问 类外不可以访问
//儿子也可以访问父亲中保护的内容:
//如:父亲的车可以作为保护权限,
//私有权限 privated:成员类内可以访问 类外不可以访问
//儿子不能访问父亲的私有内容
//如银行卡密码
class Person
{
public :
//公共权限
string m_Name;//姓名
protected:
//保护权限
string m_Car;//汽车
private:
int m_Password;//银行卡密码
private:
void func()
{
m_Name="张三";
m_Car= "拖拉机";
m_Password = 123456;
}
};
int main()
{
Person p1;//实例化具体的对象
p1.m_Name = "李四";
//p1.m_Car = "奥迪";//保护权限的内容,在类外不可以访问
//p1.func();
system("pause");
return 0;
}
struct和class的区别
区别:
struct和class的默认访问权限不同
struct:默认权限为公开的
class:默认权限为私有的
#include<iostream>
#include<string>
using namespace std;
//struct和class的区别
class C1
{
int m_A;//默认权限为私有;
};
struct C2
{
int m_A;
};
int main()
{
C1 c1;
C2 c2;
//c1.m_A=100;
c2.m_A=100;
system("pause");
return 0;
}
成员属性设为私有——理论
优点一:将所有成员属性设置为私有,可以自己控制读写权限
优点二:对于写权限,我们可以检测数据的有效性
#include<iostream>
#include<string>
using namespace std;
//struct和class的区别
class C1
{
public:
//行为公共
void getname(string a)
{
name = a;
}
string outputname()
{
return name;
}
void getage(int b)
{
if((b>0)&&(b<150))
{
age= b;
}
else
cout<<"你个老妖精"<<endl;
}
int outputage()
{
return age;
}
int outputcard()
{
card = 0;
return card;
}
//属性私有
private:
string name;
int age;
int card;
};
int main()
{
C1 man;
man.getname("管军");
cout<<man.outputname()<<endl;
man.getage(100);
cout<<man.outputage()<<endl;
cout<<man.outputcard()<<endl;
system("pause");
return 0;
}
成员属性设为私有—案例一
1:设计立方体类(cube)
求出立方体的面积和体积
分别求全局函数和成员函数判断两个立方体是否相等
#include<iostream>
#include<string>
using namespace std;
//struct和class的区别
class Cube
{
public:
//行为公共
void getm_L(int a)
{
m_L = a;
}
int outputm_L()
{
return m_L;
}
void getm_W(int b)
{
m_W = b;
}
int outputm_W()
{
return m_W;
}
void getm_H(int c)
{
m_H = c;
}
int outputm_H()
{
return m_H;
}
int outarea()
{
area = (m_L*m_W+m_L*m_H+m_W*m_H)*3;
return area;
}
int outvolume()
{
volume = m_L*m_H*m_W;
return volume;
}
//成员函数
void compare2(Cube &a)
{
if(a.outputm_L()==m_L&&a.outputm_W()==m_W&&a.outputm_H()==m_H)
{
cout<<"成员函数:两圆的面积是一致的"<<endl;
}
else
{
cout<<"成员函数:两圆的面积是不一致的"<<endl;
}
}
//属性私有
private:
int m_L;
int m_W;
int m_H;
int area;
int volume;
};
//全局函数
bool compare(Cube &a,Cube &b)//引用做函数参数
{
if(a.outputm_L()==b.outputm_L()&&a.outputm_W()== b.outputm_W()&&a.outputm_H()==b.outputm_H())
{
return true;
}
else
{
return false;
}
}
int main()
{
Cube c1;
Cube c2;
c1.getm_L(1);
c1.getm_W (2);
c1.getm_H(3);
cout<<"c1的面积为:"<<c1.outarea()<<endl;
cout<<"c1的体积为:"<<c1.outvolume()<<endl;
c2.getm_L(1);
c2.getm_W (2);
c2.getm_H(3);
cout<<"c2的面积为:"<<c2.outarea()<<endl;
cout<<"c2的体积为:"<<c2.outvolume()<<endl;
bool ret = compare(c1,c2);
if(ret == 1)
{
cout<<"全局函数:两圆的面积是一致的"<<endl;
}
else
{
cout<<"全局函数:两圆的面积是不一致的"<<endl;
}
c1.compare2(c2);
system("pause");
return 0;
}
成员属性设为私有—案例二
点和圆的关系,要求设计一个圆类(circle)和一个点类(point),计算点和圆的关系
两个头文件:
pointer的头文件(pointer.h):
#pragma once
#include<iostream>
using namespace std;
class Pointer
{
public:
void setX(int a);
int getX();
void setY(int b);
int getY();
private:
int X;
int Y;
};
pointer的声明(pointer.cpp):
#include"pointer.h"
void Pointer::setX(int a)
{
X = a;
}
int Pointer::getX()
{
return X;
}
void Pointer::setY(int b)
{
Y = b;
}
int Pointer::getY()
{
return Y;
}
circle的头文件(circle.h):
#pragma once // 防止他的头文件重复包含
#include<iostream>//标准输入输出头文件
#include"pointer.h"
using namespace std;//标准命名空间
class Circle
{
public:
void setcenter(Pointer &a);
Pointer center();
void judge(Circle &a,Pointer &b);
void radius(int a);
int radius();
private:
int m_R;
Pointer m_Center;
Pointer m_Point;
};
circle的声明(circle.cpp):
#include"circle.h"
void Circle::setcenter(Pointer &a)
{
m_Center = a;
}
Pointer Circle::center()
{
return m_Center;
}
void Circle::judge(Circle &a,Pointer &b)
{
if((a.center().getX()-b.getX())-(a.center().getX()-b.getX())+(a.center().getY()-b.getY())*(a.center().getY()-b.getY())-a.radius()*a.radius()>0)
{
cout<<"点在圆外"<<endl;
}
if((a.center().getX()-b.getX())-(a.center().getX()-b.getX())+(a.center().getY()-b.getY())*(a.center().getY()-b.getY())-a.radius()*a.radius()==0)
{
cout<<"点在圆上"<<endl;
}
if((a.center().getX()-b.getX())-(a.center().getX()-b.getX())+(a.center().getY()-b.getY())*(a.center().getY()-b.getY())-a.radius()*a.radius()<0)
{
cout<<"点在圆内"<<endl;
}
}
//设置半径
void Circle::radius(int a)//类名加上::是为了标注其为成员函数
{
m_R=a;
}
int Circle::radius()
{
return m_R;
}
主函数:
#include<iostream>
#include"pointer.h"
#include"circle.h"
using namespace std;
int main()
{
Circle num1;
Pointer any;
cout<<"请输入圆的半径:";
int a ;
cin>>a;
num1.radius(a);
cout<<"请输入圆心坐标:"<<endl;
Pointer center;
cin>>a;
center.setX(a);
cin>>a;
center.setY(a);
num1.setcenter(center);
system("cls");
while(1)
{
cout<<"圆的半径为:"<<num1.radius()<<endl;
cout<<"圆心坐标为:("<<num1.center().getX()<<","<<num1.center().getY()<<")"<<endl;
cout<<"请输入你想查询的点的坐标 :"<<endl;
cin>>a;
any.setX(a);
cin>>a;
any.setY(a);
cout<<"你查询的点为:("<<any.getX()<<","<<any.getY()<<")"<<endl;
num1.judge(num1,any);
system("pause");
system("cls");
}
return 0;
}
对象的初始化和清理
生活的案例:电子产品的出厂设置
c++中的面向对象来源于生活,每个对象也都有自己的初始化设置一会对象销毁前的清理数据的设置
构造函数与析构函数:
对象的初始化和清理也是两个非常重要的安全问题;
一个对象或没有初始状态,对其使用后果是未知的
同样使用完一个对象或变量,没有及时清理,也会造成一定的安全问题!
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,且无需手动调用
析构函数:主用作用域对象销毁前系统自动调用,执行一些清理工作!
#include<iostream>
using namespace std;
//对象的初始化和清理
//1、构造函数,进行初始化操作
class Person
{
public:
//1.1、构造函数
//没有返回值,不用写void
//函数名与类名相同
//构造函数可以有参数,可以发生重载
//创建对象的时候,构造函数会自动调用,而且只调用一次
Person()
{
cout<<"person的构造函数"<<endl;
}
//1.2
//析构函数:进行清理的操作!
//没有返回值,不写void
//函数名与类名相同,在名称前加~
//析构函数是不可以有参数的,不可以发生重载
//对象销毁前。会自动调用析构函数的,而且只会调用一次
~Person()
{
cout<<"person的析构函数"<<endl;
}
};
//对象创立在栈区,构造与析构都是必须实现的,如果我们不提供,编译器会提供一个空实现的构造与析构
void function()
{
Person sa; //在栈上的数据,在函数执行完,释放该对象
}
void main()
{
//void function();
Person as;
//system("pause");
}
构造函数的初始化列表
构造函数列表以一个冒号开始,接着以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号里的初始化式。
class CExample {
public:
int a;
float b;
//构造函数初始化列表
CExample(): a(0),b(8.8)
{}
//构造函数内部赋值
CExample()
{
a=0;
b=8.8;
}
};
上面的例子中两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。
注意点:
初始化列表的成员初始化顺序:
C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
拷贝构造函数的调用时机
c++拷贝构造函数调用时机:
1:使用一个已经创建完毕的对象来初始化一个新
的对象
2:值传递的当时给函数参数值
3:以值方式返回局部变量
#include<iostream>
using namespace std;
//c++拷贝构造函数调用时机:
class person
{
public:
person()
{
cout<<"person的默认函数构造"<<endl;
}
person(int age)
{
cout<<"person的有参数函数构造"<<endl;
m_age = age;
}
person(person &p)
{
cout<<"person的拷贝数函数构造"<<endl;
m_age = p.m_age;
}
~person()
{
cout<<"person的析构函数调用"<<endl;
}
int m_age;
};
//1:使用一个已经创建完毕的对象来初始化一个新的对象
void test01()
{
person p1(20);
person p2(p1);
cout<< "p2的年龄为: "<<p2.m_age<<endl;
}
void dowork(person p)
{
p.m_age = 20 ;
cout<< "形参p的年龄为: "<<p.m_age<<endl;
}
//2:值传递的当时给函数参数值
//值传递的本质为拷贝临时的副本
void test02()
{
person p;
p.m_age = 10;
cout<< "实参p的年龄为: "<<p.m_age<<endl;
dowork(p);
}
//3:以值方式返回局部变量
person dowork2()
{
person p;
p.m_age = 10;
cout<<(int)&p<<endl;
cout<<(int*)&p<<endl;
return p;//局部变量会在函数执行完立马进行释放;
}
void test03()
{
person p = dowork2();
cout<<(int)&p<<endl;
cout<<(int*)&p<<endl;
}
int main()
{
//test01();//如果忘了函数忘记写括号,他会报“run.cpp(39): warning C4551: 缺少参数列表的函数调用”
//test02();
test03();
system("pause");
return 0;
}
构造函数的调用规则
多态—虚函数和纯虚函数
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚函数
虚析构和纯析构共性:
可以解决父类指针释放子类对象
都需要具体的函数实现
虚析构和纯虚虚构的区别
如果是纯虚析构,该类属于抽象类,无法实例化对象
#include<fstream>
#include<iostream>
#include<string>
using namespace std;
//二进制文件:读文件
class Animal
{
public:
Animal()
{
cout<<"Animal的构造函数"<<endl;
}
//纯虚函数
virtual void speak() = 0;
//利用虚析构可以解决父类指针释放子类对象不干净的问题
/*virtual ~ Animal()
{
cout<<"Animal的析构函数"<<endl;
}*/
virtual ~ Animal()= 0;//纯虚析构需要声明也需要实现,因为编译器要预留空间给释放堆区
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
};
Animal::~Animal()
{
cout<<"Animal的纯虚析构函数"<<endl;
}
class Cat:public Animal
{
public:
Cat(string name)
{
cout<<"cat函数的构造代码"<<endl;
m_name= new string(name);
}
virtual void speak()
{
cout<<*m_name<<"小猫在说话"<<endl;
}
~Cat()
{
if(m_name!=NULL)
{
cout<<"cat函数的析构代码"<<endl;
delete m_name;
m_name = NULL;
}
}
string *m_name;
};
void test01()
{
Animal *animal = new Cat("汤姆");
animal->speak();
//父类指针在析构时候,不会调用子类中的析构函数,导致子类中如果有堆区属性出现内存泄漏的情况!
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束,就会被释放
通过文件可以将数据持久化
c++中对文件操作需要包含头文件
文件类型分为两种
1:文本文件:文件以文本ASCII码存储在计算机里
2:二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类
1:ofstream
2:ifstream
3:fstream
文本文件
#include<fstream>//头文件的包含
#include<iostream>
using namespace std;
//文本文件:写文件
void test01()
{
//1、包含头文件 fstream
//2、创建流对象
ofstream ofs;
//指定打开方式
ofs.open("text.txt",ios::out);
//4:写内容
ofs<<"姓名:猪"<<endl;
ofs<<"年级:23"<<endl;
ofs<<"身高:12"<<endl;
//5.关闭文件
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
#include<fstream>//头文件的包含
#include<iostream>
#include<string>
using namespace std;
//文本文件:读文件
void test01()
{
//1、包含头文件 fstream
//2、创建流对象
ifstream ifs;
//3:打开文件,并且判断是否打开成功
ifs.open("text.txt",ios::in);
if(!ifs.is_open())
{
cout<<"文件打开失败"<<endl;
return;
}
//4:读数据
//第一种
/*char buf[1024] = {0};
while(ifs>>buf)
{
cout<<buf<<endl;
}*/
//第二种
/*char buff[1024]= {0};
while(ifs.getline(buff,sizeof(buff)))
{
cout<<buff<<endl;
}*/
//第三种
string buf;//字符串
while(getline(ifs,buf))
{
cout<<buf<<endl;
}
//第四种
//char c;
//while((c = ifs.get())!=EOF )//EOF: end of file
//{
// cout << c; //不能加上endl;因为读取的char,即是一个字节,此时我们加上endl,会人为的把它进行分割
//}
//5.关闭文件
ifs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
二进制文件
以二进制的方式对文件进行读写操作
打开方式:
iOS::binary
写
二进制方式写文件主要利用流对象调用成员函数write
函数原型:
ostream& write(const char* buffer,int len);
参数解释:字符串指针buffer指向内存中一段存储空间,len是读写的字节数
#include<fstream>
#include<iostream>
#include<string>
using namespace std;
//二进制文件:写文件
class person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
//1、包含头文件 fstream
2、创建流对象
//ofstream ofs;
3:打开文件,并且判断是否打开成功
//ofs.open("person.txt",ios::out|ios::binary);
//2和3合并
ofstream ofs("person.txt",ios::out|ios::binary);
//4:写文件
person p ={"张三",12};
ofs.write((const char* )&p,sizeof(person));
//5.关闭文件
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:
istream& read(char *buffer,int len)
#include<fstream>
#include<iostream>
#include<string>
using namespace std;
//二进制文件:读文件
class person
{
public:
char m_n[64];//读取数据的时候,他是按照字节依次读取的,若这边的数组大小与写的大小不一致,会存在失败的可能
int m_a;
};
void test01()
{
//1、包含头文件
//2、创建流对象
ifstream ifs;
//3、打开文件,并判断是否成功
ifs.open("person.txt",ios::in|ios::binary);
if(!ifs.is_open())
{
cout<<"文件打开失败"<<endl;
return;
}
//4、读相关数据
person p;
ifs.read((char*)&p,sizeof(person));
cout<<"名字:"<<p.m_n<<"年龄:"<<p.m_a<<endl;
//5、关闭文件
ifs.close();
}
int main()
{
test01();
system("pause");
return 0;
}