首页 > 技术文章 > C++

leon618 2020-10-09 00:15 原文

C++面向过程

一、Hello World

#include <iostream>
#defind day 7 // 宏常量
using namespace std;
int main(){
   // 常量,无法修改
   const int a =10;
   // 标识符区分大小写
   int A = 30;
   cout<<"hello world"<<endl;
   return 0;
}

二、数据类型

2.1 基本类型

数据类型占用空间
short 2字节
int 4字节
long windows 4字节 linux 4字节(32位)8字节(64位)
long long 8字节
float 4字节
double 8字节
char 1字节
bool 1字节
bool 1字节

sizeof(数据类型or变量)获取数据类型占用内存空间大小

cout<<"语句"<<endl;

cin>>变量>>endl;

goto FLAG;

FLAG:xxxx

system("命令");

2.1 数组

数组是内存中一块连续空间,数组名称是数组首地址

元素数据类型 数组名称[] = {元素1,元素2....}


int arr[] = {1,2,3};
// 输出元素个数
cout>>sizeof(arr)/sizeof(arr[0])<<endl;
// 二维数组第二个参数不能省略,2行3列
int arr[][3]={1,2,3,4,5,6}
// 行数
cout<<sizeof(arr)/sizeof(arr[0])<<endl;
// 列数
cout<<sizeof(arr[0])/sizeof(arr[0][0])<<endl;

2.2 指针

指针在32位操作系统占4字节空间,在64位操作系统占8字节空间

// 空指针,指向编号为0地址,没有访问权限
int * p = NULL;
// 野指针,指向非法内存空间(不是当前程序分配内存空间)
int * p = (int *)0x1100;
// 常量指针,指针修饰的变量可以改变指向,但是无法改变指针的值
const int * p = &a;
// 指针常量,指针指向不可以改,但是指针的值可以改
int * const p = &a;
// 将a地址复制一份传给p
void fun(int* p){
   
}
fun(&a);
// 在C语言中有void * 指针(万能指针)而C++中取消了
int a = 10;
void * p = &a;
// 万能指针可以引用任何地址,但使用时需要转换到实际类型
*(*int)p = 10;
// 指针数组,数组每一个元素都是一个指针
int * arr[3] = {&a,&b,&c};
// 可以通关指针修改常量值
const int a = 10;
int * p = &a;
*p=20;

指针变量只能进行加减操作,加减单位是存储地址中存储的实际类型占用的内存空间值

函数不要返回局部变量地址,因为函数调用完成会销毁,局部变量第二次调用会销毁

2.3 函数

函数定义在使用之后,则函数一定要先声明再调用,声明可以多次,定义只能一次。 声明函数可以给默认参数。 函数声明时可以不写形参,只写类型,就是占位参数。 函数在类外部也支持重载,同一作用域,函数名称相同,参数类型。 函数重载可以通过const区别

返回值类型 函数名 (参数类列表){
   函数体语句;....
   return 返回值或表达式;
}

// 防止头文件重复包含
#pragma once
// 从c++指定类库加载
#include<iostream>
#include“io.h”
// 终止函数执行
exit(0);

函数分文件编写:.h文件写函数声明和引用其它类库 .cpp文件写函数定义和声明函数的.h文件

2.4 结构体

函数参数是结构体,建议用引用传递,如果是值传递则会复制一份相同的结构体,如果是引用传递则只会复制一份地址值;防止在函数中修改结构体值,可以用常量结构体指针

struct 结构体名{
   成员列表...
} [变量名];

// 结构体指针通过->符号访问结构体变量的值
struct studet{
   string name;
   int age;
} *p;
cout<<p->name<<endl;
// 为结构体赋值
struct student = {"cai",12};
// 防止在函数中修改结构体变量.并且引用传递不会复制结构体副本,只会复制地址值,减少内存占用
void func(const student * stu){
   
}

2.5 共用体

不能在定义共用体初始化,不能用共用体变量作为函数参数,可以用共用体指针作为参数,也可以定义共用体数组

union 共用体类型{
   成员变量...
}[变量名];

2.6 枚举

对枚举元素做常量处理,不能进行赋值,默认有序0,1,2,3....,可以用来比较

enum week {sun,mon};

C++面向对象

一、内存分区模型

  • 代码区:存放函数二进制代码,由操作系统管理,是共享只读的

  • 全局区:存放全局变量和静态变量(static修饰)以及全局常量,程序结束后由操作系统自动释放(const局部常量和局部变量放在一起)

  • 栈区:编译器自动释放,存储函数参数值、局部变量等

  • 堆区:程序员分配和释放,若不释放,则在程序结束后自动释放

通过 new 可以在堆上分配内存,通过指针引用(指针变量在栈上分配),通过 delete 删除堆空间内存

通过typeid()返回类型

int a[] = new int[10];
delete[] a;
// 创建引用变量
int a = 10;
// 相当于a的别名,引用必须初始化,且初始化后不可改变
int &b = a;
int c = 20;
// 并不是改变应用是改变b和a的值
b = a;
// 引用传递使用的是原值,而不是原值的拷贝
void func(int &a){
   
}
func(a);

// 返回值是引用可以作为左值
int& func(){
   // static 修饰变量放在常量区,防止函数返回后清除
   static int a = 20;
   return a;
}
func() = 30;
// 引用本质上是指针常量,一旦初始化不能改变
// 等价于 int const * ref = &a;
int &ref = a;
// 等价于 *ref = 10;
ref = 10;
// 引用值必须是一个合法内存空间,不能是值常量
const int &a = 10;
// 在堆区分配内存
int a = new int(10);

二、面向对象

public:类内外都可访问 protected:类外不可访问,子类可以访问父类 private:类外不可访问,子类不可访问(默认)

编译器提供 构造函数析构函数 默认是空,创建对象系统自动调用 构造函数,对象销毁钱自动调用 析构函数

C++返回值如果是对象值,则会返回对象的浅拷贝(与原对象变量地址不同),值传递参数,返回值参数,使用对象初始化另外一个对象会默认调用拷贝构造函数 C++为每个空对象分配一个字节的空间,为了区分空对象占用独一无二的内存空间,如果不是空,则会占用成员变量的空间大小 this隐含在每一个非静态函数 c++给一个类添加四个默认函数:默认构造函数默认析构函数默认拷贝构造函数赋值运算符重载;当用另外一个同类对象初始化对象时调用的是拷贝构造函数;初始化之后通过=赋值对象调用的是 赋值运算符重载

// 构造函数,可以有参数可以重载
类名(){}
// 析构函数,无参数不可重载
~类名(){}
//拷贝构造函数
类名()}{}

浅拷贝带来的问题是在析构函数中对堆区内存重复释放

class Person{
   public:
   // 为a、b指定初始值
   Person(int a,int b):m_a(a),m_b(b){
       m_a  =  new int(a);
       m_b = new int(b);
  }
   Person(int a){
       this->m_a=a;
}
   int m_a;
   int m_b;
   ~Person(){
       delete m_a;
       delete m_b;
  }
   static int m_c;
   static void func(){
      // 防止空指针
       if(NULL==this){
           return;
      }
       // 属性前面默认加上this
       m_c = 10;
       cout<<"静态方法调用"<<endl;
  }
   void say(){
       cout<<"静态方法调用"<<endl;
  }
}
int main(){
   Person p(1,2);
   // 访问静态函数
   p.func();
   Person::func();
   return 0;
   // 8字节,static成员变量放在静态区
   cout<<sizeof(p)<<endl;
   Person *person = NULL;
   person->say();
}

const修饰成员函数叫 常函数 常函数不能修改成员属性值,成员属性加上mutable后可以被常函数修改,const修饰的对象叫做 常对象 只能调用 常函数

class Person{
   public:
   mutable int m_A;
   int m_B;
   // this指针指向的值不可修改,即m_B不可修改
   void showPerson () const{
       this->m_A=10;
  }
   void say () {
       cout<<1<<endl;
  }
}
void main(){
   const Person p;
   // 常对象可以调用常函数,但是不能调用普通函数,因为普通函数会改变属性值
   p.showPerson();
}

友元:让一个函数或者类访问另一个函数的私有成员

  • 全局函数做友元

class Student{
   // 友元函数是全局函数,不被类内的任何修饰符限制(即使有)
   // 类内声明,类外定义
friend void say*(Student *stu);
   // 类内声明定义,全局函数
   friend void info(){
       cout<<"info"<<endl;
  }
private:
   int m_Age;    
}
void say(Student *stu){
   cout<<stu->m_Age<<endl;
}
void main(){
   Student stu;
   stu.say(&stu);
}
  • 类做友元

// 声明类
#include<iostream>
#include<string>
using namespace std;
class Teacher;
class Student{
   // 友元类可以访问当前类私有成员
friend class Teacher;
private:
   string m_Name;
   int m_Age;
   public:
   Student();
   void say();
}
// 类外定义构造函数
Student::Student(){
   m_Name = "张三";
   m_Age = 12;
}
class Teacher{
   public:
   Teacher();
   Student * stu;
   void visit();
}
// 类外定义构造函数
Teacher::Teacher(){
   stu = new Student;
}
// 类外定义函数
Teacher::visit(){
cout<<stu->m_Name()<<endl;
cout<<stu->m_Age()<<endl;
}
void main(){
   Teacher teacher;
   teacher.visit();
}
  • 成员函数做友元

#include<iostream>
#incldue<string>
using namespace std;
class Building;
class GoodGay{
   public:
   GoodGay();
   void visit();
   Building *building;
}
GoodGay::GoodGay(){
   building = new Building;
}
GoodGay::visit(){
   // 该函数作为Building友元,可以访问私有成员
   cout<<building->m_BedRoom<<endl;
}
class Building{
   // 告诉编译器指定类下的指定函数作为当前类友元,可以访问当前类私有成员
   friend void GoodGay::visit();
   public:
   Building();
   string m_SittingRoom;
   private:
   string m_BedRoom;
}
Building::Building(){
m_SittingRoom = "客厅";
   m_BedRoom = "卧室";
}

void main(){
   
}

运算符重载

class Person{
   public:
   Person operator+(Person &p){
       a = new int(10);
  }
   int m_Num;
   int* a;
   // 前置操作符重载
   Person& operator++(){
       m_Num++;
       return *this
  }
   // 后置操作符重载,(占位参数表示是后置运算符)
   Person operator++(int){
       Person temp = *this;
       m_Num++;
       return temp;
  }
   
};
// 第一个参数是调用的对象
// 全局重载
ostream operator<<(ostream &cout,Person p){
   cout<<p.name<<endl;
   return cout;
}
// 深拷贝
Person& operator=(Person &p){
   if(a!=NULL){
delete a;
      a = NULL;
  }
   a = new int(*(p.a));
   return *this;
}

C++支持多继承,但一般不使用;父类中的所有属性都会继承到子类,只是私有成员不可见

// 公共继承 父类属性全部继承到子类,但私有不可见
class Son:public Base;
// 公共继承 父类属性全部继承到子类,公共属性变成保护,但私有不可见
class Son:protected Base;
// 公共继承 父类属性全部继承到子类,公共和保护属性变成私有属性,但私有不可见
class Son:private Base;
// 访问父类属性
class Base{
   public:
   int m_A;
   Base(){
       m_A = 10;
  }
   static m_B;
   static void say(){}
};
// 类外初始化静态变量
int Base::m_B = 20
class Son:public Base{
};
int main(){
   Son s;
   Son::Base::m_B;
   Son::Base::say();
   s.Base::m_A;
   return 0;
}
// 多继承
class A:public B,public C;
// 当多继承的父类出现重复定义属性,必须通过::区分不同域对象属性
// 利用虚继承,解决父类重复属性;虚基类保存一个
class A{
   public:
   int a;
};
class B:virtual public A{};
class C:virtual public A{};
// 此时D两个属性a(从B、C继承)都只是一个指针(vbprt),指向了虚基类表,表中记录了虚基类属性(A中的a属性)偏移量
class D:public B,public C{};

多态分为两类

  • 静态多态:函数重载、运算符重载,函数重载;地址早绑定,编译阶段确定

  • 动态多态:父类引用指向子类对象;地址晚绑定,运行阶段确定

    1. 有继承关系

    2. 重写父类虚函数(virtual)

    3. 父类指针或者引用 指向子类对象

class Animal{
   public:
   // 地址早绑定
   void speak(){
       cout<<"动物在说话"<<endl;
  }
   // 地址晚绑定
   virtual void speak(){
       cout<<"动物在说话"<<endl;
  }
};
class Cat:public Animal{
   public:
   void speak(){
       cout<<"猫在说话"<<endl;
  }
};
void doSpeak(Animal &animal){
   animal.speak();
};
int main(){
   Cat cat;
   // 地址早绑定,编译时就确定会调用父类方法
   doSpeak(cat);
   return 0;
}

动态多态原理:虚函数记录一个vfprt(虚函数指针)指针,指向虚函数表,虚函数表中记录函数实现入口地址;子类重写虚函数后覆盖子类的虚函数表中记录的入口地址(父类虚函数指针没变,还是指向以前的虚函数表地址)

父类中的虚函数可以不实现,直接赋值0就是 纯虚函数。即:virtual 返回值类型 函数名(参数列表)=0;当类中有一个纯虚函数,该类被称为 抽象类;抽象类无法实例化对象,子类如果不实现纯虚函数也会成为 抽象类

C++高级

c++中用模板实现泛型编程,分为 函数模板类模板

函数模板

函数模板中类型参数不能有默认参数

普通函数函数模板 重名调用规则:

  1. 如果两者都可以调用,则优先调用普通函数

  2. 通过空参数列表强制调用函数模板,例:int<>(1)

  3. 函数模板可以重载

  4. 如果 函数模板 更匹配则调用 函数模板

template<typename T>
void func(T t){
   
};
// 提供具体类型实现规则
template<> void func(Person &p){
   
};
// 显示指定类型,可以发生隐式类型转换(向上转型)
func<int>(1);
// 自动类型推导,如果使用自动类型推导,则不会发生隐式类型转换(向上转型)
func(1)

类模板

类模板 没有自动类型推导;类模板 在类型参数列表中可以定义默认参数;普通成员函数在创建对象时候就就创建,类模板 中成员函数在调用时创建,动态绑定;类模板 中的类型参数必须在创建对象时指定

#include<iostream>
#include<string>
using namespace std;
template<class NameType,class AgeType = int>
class Person{
public:
   Person(NameType name,AgeType age){
       this->m_Name=name;
       this->m_Age=age;
  }
   // 类内声明,类外实现
   void info();
private:
   NameType m_Name;
   AgeType m_Age;
};
// 类外实现模板函数,即使不使用类型参数也要加上限定
template<class T1,class T2>
// 类外实现成员函数必须通过::前面加上类名作为作用域
void Person<T1,T2>::info(){
   
}
int main(){
   Person<string> p("",1)
   return 0;
}

类模板 中定义友元函数

#include <iostream>
#include <string>
using namespace std;
template<class T1,class T2>

template<class T1,class T2>
class Person;  
// 友元函数模板类外实现
template<class T1,class T2>
void info2(Person<T1,T2>p){
 
}
template<class T1,class T2>
class Person{
   // 类模板内声明定义友元函数
   friend void info1(Person<T1,T2> p){}
   // 类模板内声明友元函数,类外定义;需要让编译器提前知道有类外实现代码
   // 加<>空参数列表表明是函数模板的类外实现
   friend void info2<>(Person<T1,T2> p);
};

仿函数

类中重载()运算符,将对象像函数一样调用,可以实现闭包;重载()并且返回值是bool类型,则将该类称为谓词

推荐阅读