二.C++核心编程
Posted AceYA
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二.C++核心编程相关的知识,希望对你有一定的参考价值。
一.内存的分区模型
代码区:存放函数体的二进制代码,由操作系统管理。
全局区:存放全局变量和静态变量以及常量。
栈区:编译器自动分配释放,存放函数的参数值,局部变量等。
堆区:程序员分配和释放,若不操作,程序结束时由系统回收。
1.程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域。
代码区:
存放cpu执行的机器指令;
代码区是共享的,意义在于对于频繁被执行的程序,只需要在内存中有一份即可;
代码区是只读的,防止程序意外的修改它的指令。
全局区:
全局变量,static静态变量,全局常量,字符串常量存放于此;
该区域数据在程序结束后由操作系统释放。
2.程序运行后
栈区:
编译器自动分配释放,用完就会被释放,存放函数的参数值,局部变量等。
堆区:
程序员分配和释放,若不操作,程序结束时由系统回收。
在c++中主要利用new在堆区开辟内存。
float *p = new float(3.2f) // 返回存放该数据类型的指针
3.new操作符
new在堆区开辟内存,返回一个指针对象,delete释放内存。
// 1.开辟普通变量
int * p = new int(100);
cout << *p << endl; // 100
cout << *p << endl; // 100
delete p; // 释放变量
cout << *p << endl; // 报错
// 2.开辟数组
int * arrp = new int[10];// 10为数组元素个数
for (int i = 0; i < 10; i++){
arrp[i] = i*2;
}
for (int i = 0; i < 10; i++) {
cout<< arrp[i]<<endl;
}
delete [] arrp; // 释放数组时加[],不然只会释放首元素
// 3.补充
涉及到 delete 释放指针时,要注意被释放的指针是不是外部其他指针传进来的,避免删除多个对象公用的指针
二.引用
1.语法
作用:给变量起别名,原名和别名指向同一个内存地址。
语法:数据类型 &别名 = 原名;
int a = 1;
int& b = a;
cout << "a =" << a <<endl; // a=1
cout << "b =" << b << endl; // b=1
b = 2;
cout << "a =" << a << endl; // a=2
cout << "b =" << b << endl; // b=2
2.注意事项
引用必须初始化。比如int& b;是错误的
引用在初始化以后不可以再改变。比如下面:
int a = 1;
int b = 2;
int& c = a;
cout << "a =" << a << endl; // 1
cout << "b =" << b << endl; // 2
cout << "c =" << c << endl; // 1
c = b; // 这部是赋值,等同于 a = c = b =2
cout << "a =" << a << endl; // 2
cout << "b =" << b << endl; // 2
cout << "c =" << c << endl; // 2
3.引用做函数参数--引用传递
前言:之前数据传递分为 值传递 和 地址传递,值传递时形参不能修饰实参。
作用:利用引用传递让形参修饰实参,简化指针修饰。
// 值传递
void swap01(int a,int b){
int temp = a;
a = b;
b = temp;
}
// 地址传递
void swap02(int *a,int *b){
int temp = *a;
*a = *b;
*b = temp;
}
// 引用传递
void swap03(int &a,int &b){// 形参列表里的a和b是实参c和d的别名
int temp = a;
a = b;
b = temp;
}
int main(){
int a =10;
int b =20;
int c =10;
int d =20;
swap01(a,b); // 值传递。a和b没有交换
swap02(&a,&b); // 地址传递。a和b交换
swap03(c,d); // 引用传递。c和d交换
return 0;
}
4.引用做函数返回值
注意:不能返回局部变量引用
// 返回静态变量引用
int & test02(){
static int a = 20;
return a;
}
int main(){
int &b =test02();
return 0;
}
5.引用本质
本质是内部实现一个指针常量 int * const p = &a;
int main(){
int a = 10;
int &ref = a; // 本质:int * const ref = &a;
ref = 20; // 本质:a = *ref = 20;
cout<< "a:" << a <<endl; //20
cout<< "ref:" << ref <<endl;//20
return 0;
}
6.常量引用
作用:常量引用主要用来修饰形参,防止误操作,防止修改数据。
或者可以使引用指向一个临时常量。
void func(const int &ref){
ref = 20;// 报错
cout<<ref<<endl;//10
}
int main(){
int a = 10;
func(a);
return 0;
}
三.函数提升
1.函数的默认参数
默认参数统一写到形参列表的后半部分;
如果有函数声明时,函数实现和函数声明只能有一个可以有默认参数(因为会冲突)。
int func(int a, int b=10, int c=20){
return a+b+c;
}
2.函数占位参数
函数形参列表可以有占位参数,但是调用时必须补全
语法:void func(数据类型){}
void func(int a, int){}
func(10,20);
3.函数重载
作用:提高复用性
重载条件:
函数名相同
形参顺序,类型,个数不同
与返回值无关
在同一个作用域下
注意:
当重载函数有常量引用和普通引用时,分情况调用
{
void fun(int &a){}
void fun(const int &a){}// 给a取别名,只不过不能需改
int a = 1;
func(a);//执行void fun(int &a){}
func(100);//执行void fun(const int &a){}
}
尽量避免有默认参数的重载
四.类和对象
1.三大特性
封装、继承、多态。
2.权限修饰符
public 可以被任意实体访问
protected 只允许子类及本类的成员函数访问
private 只允许本类内部的成员函数访问
3.对象的初始化和清理
(1).语法
class 类名{
权限修饰符:
静态成员;
普通成员属性;(一般为private)
权限修饰符:
静态成员方法
普通成员方法
set,get,其他方法
};
例如:
class Student{
public:
int age;
public:
void eat(){}
};
注意:class和struct的区别
class默认修饰符是private
struct默认修饰符是public
(2).构造函数和析构函数
构造函数:创建对象时为对象成员属性赋值。编译器自动调用。
析构函数:对象销毁前系统自动调用,执行一些清理工具。
不提供这两个函数时,编译器会提供空实现的这两个函数。
构造函数:类名(){}; 无返回值也不写void,有参无参都可以,可以重载。创建对象时自动调用。
析构函数:~类名(){}; 无返回值也不写void,无参。销毁对象前自动调用。
例如:
class Person{
string name;
int age;
public:
// 构造函数
Person(){}//无参构造
Person(string name){ name = name;}//有参构造1
Person(string name, int age){ name = name;age = age;}//有参构造2
// 析构函数
~Person(){}
}
(3).构造函数详解
1.构造函数分类:
按参数分类:有参构造和无参构造
按类型分类:普通构造和拷贝构造
拷贝构造写法:
Person(const Person &p){age = p.age;}// 取别名
拷贝构造函数使用情况:
使用一个创建完毕的对象创建新对象;
Person p1(12);
Person p2(p1);
值传递的方式给函数传参;
void work(Person p){}
Person p;
work(p);// 先调用拷贝构造函数,再传入的是一个p的拷贝,地址不同,和值传递传入实参一个道理
以值方式返回局部对象。
Person crP(){
Person p;
return p;// 先调用拷贝构造函数,再返回的是一个p的拷贝,地址不同
}
注意:拷贝函数对象中的指针部分会创建到堆区
2.创建对象:
默认构造 :Person p; // 不能使用Person p();会被认为是在声明函数
**括号法 : Pesson p1("张三",13); // 普通有参构造
Person p2(p1); // 拷贝构造
显示法 : Pesson p = Pesson("张三",22);// 不加new,c++中new返回的是一个指针
隐式转换法(不用) : Person p = {多个参数};
Person p = {"张三"};
(4).深拷贝与浅拷贝
class Person {
public:
int age;
int *height;
public:
Person() {
cout<< "默认构造函数" <<endl;
}
Person(int a, int heig) {
age = a;
height = new int(heig);
cout << "有参构造函数" << endl;
}
Person(const Person &p) {
cout << "拷贝构造函数" << endl;
age = p.age;
// 这里指针的拷贝属于浅拷贝,原先的默认实现方式,不同拷贝对象共用同一个栈区地址
// height = p.height;
height = new int(*p.height); // 这里指针的拷贝是深拷贝,不同拷贝对象有各自的栈区指针
}
~Person(){
if (height != NULL) {
delete height;
height = NULL;
}// 当拷贝对象使用浅拷贝而来时,这里会发生错误
cout << "析构函数" << endl;
}
};
int main(){
Person p1(18,160);
cout<< "p1的年龄:"<< p1.age<<" p1的身高 " << *(p1.height) <<endl;
cout<< "p1的地址:"<< (int)&p1 <<endl;
Person p2(p1);
cout << "p2的年龄:" << p2.age << " p2的身高 " << *(p2.height) << endl;
cout << "p2的地址:" << (int)&p2 << endl;
system("pause");
return 0;
}
(5).初始化列表
作用:c++提供了初始化列表,用来初始化属性,效率更高
class Person{
int p_a;
int p_b;
public:
// 传统有参构造函数Person p(10,20)
Person(int a, int b){
p_a = a;
p_b = b;
}
/*
初始化列表语法:
Person(int a, int b):p_a(值1),p_b(值2){}
Person p(10,20)
*/
Person(int a, int b):p_a(10),p_b(20){
}
};
(6).类对象作为类成员
class A{};
class B{
A a;
};
运行顺序:A构造--》B构造--》B析构--》A析构
(7).static静态成员
静态成员分为:
静态属性:
所有对象共享同一份数据;
编译阶段分配内存;
类内声明,类外初始化;
Person类内 static int id;
Person类外 int Person::id = 1;
静态函数:
所有对象共享同一个函数;
静态函数只能访问静态属性。
static void func(){}
访问:不能访问private修饰的静态成员
静态属性:
Person::id
p.id
静态函数:
Person::func();
p.func();
示例:
class Person{
public:
int age;
static int id;// 静态成员属性
static void func(){ //静态成员函数
id = 2;// 正确
// age = 20;// 报错,静态函数只能访问静态属性
}
};
int Person::id = 1; // 静态属性类内声明,类外初始化
int main(){
Person p(10);
// 1.静态属性的调用
cout<< Person::id <<endl;
cout << p.id << endl;
// 2.静态函数的调用
Person::func();
p.func();
return 0;
}
4.c++对象模型和this指针
1.成员变量和成员函数分开存储
静态成员 不属于类对象上,不占对象空间,单独开辟;
每个空对象会占一个字节空间;
成员变量 属于类对象上;
成员函数 不属于类对象上。
2.this指针
定义:
类的普通函数只会生成一个函数实例,类的多个对象c1,c2,...会共用这个函数实例,为了区分对象,
使用this指针指向 被调用的成员函数 所属的对象。
用途:
当形参和成员变量同名时,用this区分 this->age;
在普通函数中返回对象本身时,可使用
Person& func(){
return *this;
}
// 返回值是Person时,会调用拷贝构造函数,返回的是this对象的拷贝,不是原this
// 返回值是Person&时,相当于给this起了别名,还是原this对象
3.空指针访问成员函数
Person *p = NULL;
在使用this指针的地方加
if(this == NULL)
return;
5.constan修饰成员函数
常函数:
const修饰的常函数,本质上修饰的是this指针,this原来是指针常量,加const后this指向的值也不可以被修改
常函数不可以修改一般成员属性
成员属性前加mutable可以在常函数中被修改: mutable int m_b;
常对象:
const修饰对象
常对象只能调用常函数 和 mutable修饰的成员属性
示例:
class Person{
public:
int m_a;
mutable int m_b;
void showPerson() const{
// this->m_a = 100;报错
this->m_b = 100;
}
void func() {
}
};
const Persono p;
p.func();// 报错
p.showPerson();
p.m_b;
5.友元friend
作用:让函数或者类访问另外一个类的私有成员
实现方式:
全局函数做友元 friend 返回方式 函数名(形参列表);
其他类做友元 friend class 类名;
其他类的成员函数做友元 friend 返回方式 类名:: 函数名(形参列表);
注意:
使用友元时涉及到类,属性,函数的相互调用,代码书写顺序会影响编译是否通过,所以类和函数最好先进行声明再实现
比如:class Person;
void func(Person p);
class Person{
// 1.全局函数做友元
friend void func(形参);
// 2.其他类做友元
friend class Student;
// 3.其他类的函数做友元
friend void Teacher::visit02();
public:
Person(){}
private:
string name;
int age;
};
// 1.全局函数做友元
void func(Person p){
// 允许访问不会报错
cout<< p.name <<endl;
cout<< p.age <<endl;
};
// 2.其他类做友元
class Student {
Person p;
public:
Student() {}
void visit01() {
cout<< "Student访问Person的属性name:"<< p.name <<endl;
cout<< "Student访问Person的属性age:"<< p.age <<endl;
}
};
// 3.其他类的函数做友元
class Teacher {
public:
Teacher() {}
void visit02(Person p) {
cout<< "Teacher访问Person的属性name:"<< p.name <<endl;
cout<< "Teacher访问Person的属性age:"<< p.age <<endl;
}
};
6.运算符重载operator
定义:对已有的运算符重新进行定义,赋予其另一种功能,适应不同的数据类型
作用:实现两个自定义数据类型的运算
方法:
成员函数重载+号
Person operator+(Person p){
Person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
调用:
Person p3 = p1.operator+(p2);
Person p3 = p1 +p2;
成员函数重载++号
Person& operator++(){// 返回引用类型是为了链式编程
a++;
return *this;
}
调用:
Person p3 = p1.operator+(p2);
Person p3 = p1 +p2;
---------------------------------------------------------------------------------------
全局函数重载+号
Person operator+(Person &p1, Person &p2){
Person temp;
temp.a = p1.a + p2.a;
temp.b = p2.b + p2.b;
return temp;
}
调用:
Person p3 = operator+(p1, p2);
Person p3 = p1 + p2;
全局函数重载<<号
ostream& operator<<(ostream &cout, Person &p){
// ostream标准输出流对象 cout全局只有一个,所以写出&cout
cout << "a=" << p.a << " b=" << p.b << endl;
return cout;
}
调用:
operator<<(std::cout,p);
cout << p;
运算符重载operator种类(有些只能进行全局重载)
operator+
operator-
operator<
operator<<
operator<=
operator==
operator>
operator>>
operator>=
operator!=
operator new
operator new []
operator delete
operator delete[]
operator()仿函数
示例:
class Person{
public:
int a;
int b;
// 1.成员函数重载+号
Person operator+(Person p){
Person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
}
// 2.全局函数重载+号
Person operator+(Person &p1, Person &p2){
Person temp;
temp.a = p1.a + p2.a;
temp.b = p2.b + p2.b;
return temp;
}
void test(){
Person p1;
p1.a = 10;
p1.b = 10;
Person p2;
p2.a = 10;
p3.b = 10;
Person p3 = p1.operator+(p2)// 针对成员函数本质
Person p4 = operator+(p1, p2)// 针对全局函数本质
// 简化:
Person p3 = p1 + p2;
Person p4 = p1 + p2;
}
7.继承
1.语法:
class 子类名 : 继承方式 父类名{}
2.继承方式:
public :从父类继承过来的属性与方法 的 权限还是原来的级别
protected :从父类继承过来的属性与方法 的 权限统一改为protected权限
private :从父类继承过来的属性与方法 的 权限统一改为private权限
注意:父类中private修饰的内容可以被子类继承,但是子类不可以访问,被隐藏掉了
3.示例:
class Son1 : public Father{}
4.父子类构造和析构函数的执行顺序
父构造--》子构造--》子析构--》父析构
5.访问父子类中同名属性和函数的方式:属性或函数前加作用域即可
Father f;
Son s;
cout<< s.a <<endl; // 访问子类属性
cout<< s.Father::a <<endl; // 访问父类同名属性
cout<< s.func() <<endl; // 访问子类方法
cout<< s.Father::func() <<endl; // 访问父类同名方法
6.问父子类中同名静态属性或静态函数的方式:属性或函数前加作用域即可
Father f;
Son s;
cout<< s.a <<endl;
cout<< Son::a <<endl;
cout<< s.Father::a <<endl;
cout<< Son::Father::a <<endl;
cout<< s.func() <<endl;
cout<< Son::func() <<endl;
cout<< s.Father::func() <<endl;
cout<< Son::Father::func() <<endl;
7.多继承
class 子类名 : 继承方式 父类1,继承方式 父类2,继承方式 父类4,...{}
8.菱形继承
class Base{};
class A:public Base{};
class B:public Base{};
class C:public A,public B{};
菱形继承导致C类继承两份相同Base的数据,导致浪费且无意义,并且C类调用继承资源时会出现不明确问题。
利用虚继承解决菱形继承问题,使资源变为一份,底层为同一份资源指针的指向 vbptr(virtual base pointer)
将上述继承方式修改为:
class Base{};
class A:virtual public Base{};
class B:virtual public Base{};
class C:public A,public B{};
此时Base类称为虚基类
8.多态
1.概念
继承统一基类的不同子类对同一动作的不同反馈
2.分类
静态多态:函数重载 和 运算符重载数据静态多态,复用函数名
动态多态:派生类 和 虚函数实现运行时属于动态多态
区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
3.多态条件
类之间有继承关系
子类重写父类中的 virtual 虚函数
--父类函数添加 virtual 时,子类重写该虚函数,子类中虚函数表vftable会替换成子类的虚函数地址
--函数前加virtual称为虚函数,虚函数没有方法体称为纯虚函数
virtual void func() = 0;
--不加virtual时为地址早绑定
--抽象类:
带纯虚函数的类称为抽象类,抽象类只能被继承,不能被实例化。子类必须重写父类中的纯虚函数
4.多态的使用
使用父类指针或引用指向子类对象
5.虚析构和纯虚析构
使用多态时,如果子类中有属性开辟到堆区,父类的析构函数调用时无法调用到子类的析构函数。
解决方式:父类中的析构函数改为虚析构或纯虚析构
虚析构:virtual ~Base(){};
纯虚析构:virtual ~Base()=0;
Base::~Base(){};
注意:父类的纯虚函数必须在类外实现,因为父类也有自己的指针要释放
如果子类中没有堆区数据,可以不写父类的虚析构函数。
6.案例 不同厂商制作不同的硬件
#include<iostream>
using namespace std;
// 1.硬件
class CPU{
public:
virtual void calulate() = 0;
};
class VideoCard{
public:
virtual void display() = 0;
};
class Memory{
public:
virtual void storage() = 0;
};
// 2.不同厂商
class Inter :public CPU,public VideoCard,public Memory{
public:
void calulate() { cout << "InterCPU工作" << endl; }
void display() { cout << "InterVideoCard工作" << endl; }
void storage() { cout << "InterMemory工作" << endl; }
void work() {
calulate();
display();
storage();
}
};
class Lenovo :public CPU, public VideoCard, public Memory {
public:
void calulate() { cout << "LenovoCPU工作" << endl; }
void display() { cout << "LenovoVideoCard工作" << endl; }
void storage() { cout << "LenovoMemory工作" << endl; }
void work() {
calulate();
display();
storage();
}
};
int main() {
Inter inter;
inter.work();
cout<<"========================"<<endl;
Lenovo len;
len.work();
system("pause");
return 0;
}
五.文件操作
程序运行时产生的数据都属于临时数据,程序一旦结束临时数据都会被释放,通过文件可以将数据持久化。
头文件 #include<fstream>
文件类型分类:
文本文件:文件以文本的ASCII形式存储在计算机中
二进制文件:文件以文本的二进制形式存储在计算机中
操作文件的三大类:
ofstream 写操作(out)
ifstream 读操作(in)
fstream 读写操作
1.文本文件
0.打开方式:
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件,清空原内容重写
ios::ate 初始位置:文件尾at end,清空原内容重写
ios::app 追加方式写文件 append
ios::trunc 创建一个新的文件,原文件存在时删除源文件
ios::binary 读写二进制文件专用方式
out | app 在文件尾写入,不清空已经存在的数据
out | ate 在文件尾写入,清空已经存在的数据
out | trunc 删除已经存在的数据,写操作,与out模式相同
in | out 读写操作,不清空已经存在的数据
in | out | trunc 读写操作,删除文件中已经有的数据
1.写文件步骤
#include<fstream> // 导入头文件
ofstream ofs; // 创建写文件流对象
ofs.open("文件路径", 打开方式) // 打开文件
ofs<<"写入的数据"; // 写数据
ofs.close(); // 关闭文件
示例:
#include<fstream>
void test(){
ofstream ofs;
ofs.open("test.txt",ios::out);
ofs<< "写入了一行数据(默认带换行)" <<endl;
ofs.close();
}
2.读文件步骤
#include<fstream> // 导入头文件
ifstream ifs; // 创建写文件流对象
ifs.open("文件路径", 打开方式) // 打开文件并判断文件是否打开成功,返回bool
四种读取方式
ifs.close(); // 关闭文件
示例:
#include<fstream>
void test(){
ifstream ifs;
ifs.open("test.txt",ios::in);
if(!ifs.is_open()){
cout<<"打开失败"<<endl;
return;
}
// 读取方式1
char buf[1024]={0};
while(ifs >> buf){
cout<< buf <<endl;
}
// 读取方式2
char buf[1024]={0};
while(ifs.getline(buf,sizeof(buf))){
cout<< buf <<endl;
}
// 读取方式3
string buf;
while(getline(ifs,buf)){
cout<< buf <<endl;
}
// 读取方式4
char c;
while((c = ifs.get()) != EOF){//Eod of File
cout<<c;
}
ifs.close();
}
2.二进制文件
打开方式要指定为 ==ios::binary==
class Student{
public:
char m_name[64];
int age;
}
1.写文件
ostream& write(const char * buffer,int len);
字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
#include<fstream> // 1.导入头文件
void test(){
ofstream ofs; // 2.创建写文件流对象
ofs.open("student.txt",ios::out || ios::binary);// 3.打开文件
Student stu("张三",12); // 4.准备数据
ofs.write((const char *)&stu,sizeof(stu))
ofs.close(); // 5.关闭文件
}
2.读文件
istream& read(char * buffer,int len);
字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
#include<fstream> // 1.导入头文件
void test(){
ifstream ifs; // 2.创建写文件流对象
ifs.open("student.txt",ios::in || ios::binary);// 3.打开文件
if(!ifs.is_open()) return;
Student stu; // 4.准备数据
ifs.read((char *)&stu,sizeof(stu))
ifs.close(); // 5.关闭文件
}
以上是关于二.C++核心编程的主要内容,如果未能解决你的问题,请参考以下文章