二.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=10int 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(){}
        
    };

注意:classstruct的区别
    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原来是指针常量,加constthis指向的值也不可以被修改
 常函数不可以修改一般成员属性
 成员属性前加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++核心编程的主要内容,如果未能解决你的问题,请参考以下文章

C语言精华知识:表驱动法编程实践

Shell编程入门

C语言表驱动法编程实践(精华帖,建议收藏并实践)

通过《Java核心编程》探索程序设计

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

CSP核心代码片段记录