一 程序的内存模型
1.1 内存四区
代码区
存放函数体的二进制代码,由操作系统进行管理
全局区
存放全局变量,静态变量,常量(字符串常量,const修饰的全局常量)
栈区
由编译器自动分配和释放,存放函数的参数值(形参数据),局部变量,const修饰的局部变量
不要返回局部变量的地址
int * test()
{
int a = 10;
return &a;
}
堆区
由程序员分配和释放
int *p = new int(10); //在堆区创建数据,并返回其指针
1.2 new运算符
int * p = new int(10); //创建一个堆区变量
int * arr = new int[10]; //创建一个堆区数组
delete p; //删除变量
delete[] arr; //删除数组
二 C++中的引用
2.1 引用的基本使用
引用的作用:给变量起别名
int name = 10;
int &otherName = name;
2.2 注意事项
1.引用必须初始化
2.引用一旦初始化,就不可以更改
2.3 引用做函数参数
//地址传递
void mySwap01(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
mySwap01(&a,&b);
//引用传递
void mySwap02(int &a,int &b)
{
int temp = a;
a = b;
b = temp;
}
mySwap02(a,b);
2.4 引用做函数返回值
1.不要返回局部变量的引用
2.函数的调用可以作为左值
int& test01()
{
int a = 10;
return a;
}
int &ref01 = test01(); //错误,不能返回局部变量的引用
int& test02()
{
static int a = 10;
return a;
}
int &ref02 = test02(); //静态变量可以
test02 = 100; //函数作为左值,将100赋值给 a
2.5 引用的本质
引用的本质是指针常量
2.6 常量引用
const int& ref = 10; //可以直接对ref赋值为 10,并且不可以修改
void showValue(const int &value); //防止误修改value的值
3 函数高级
3.1 函数默认参数
int func(int a,int b = 20,int c = 30) //如果传入数据就用传入数据,否则用默认值
{
return a+b+c;
}
/* 注意事项
1. 如果某个位置已有了默认参数,那么从这个位置之后都必须要有默认参数
2. 函数声明和实现只能有一个有默认值
*/
3.2 函数占位参数
void func(int a,int);
void func(int a,int = 10); //占位参数也可以有默认值
3.3 函数重载
基本语法
作用:函数名可以相同,提高复用性
满足条件:1.在同一个作用域。2.函数名相同。3.函数形参不同(类型,个数,顺序)
注意事项
-
引用作为重载条件
-
函数重载遇到默认参数
void func(int &a); void func(const int &a); int a = 10; func(a); //a是变量,调用func(int &a) func(10); //10 是常量,int &a = 10 不合法,而const int &a = 10 合法,调用func(const int &a)
四 类和对象
4.1 封装
访问权限
- public:类内,类外都可以访问
- protected:类内可以访问,可以继承
- private:类内可以访问,不可以继承
class与struct的区别
-
class:默认权限为private
-
struct:默认权限为public
4.2 对象特性
构造器和析构器
class Person
{
public:
Person() //构造函数,可以有参数
{
}
~Person() //析构函数,不可以有参数
{
}
};
构造函数的分类及调用
分类:有参构造和无参构造;普通构造和拷贝构造
调用:括号法,显示法,隐式转换法
class Person
{
int age;
public:
Person()
{
}
Person(int a)
{
}
//拷贝构造:用于复制将传入对象再复制一份,用const修饰,拷贝对象与被拷贝对象类型一致
Person(const Person &p)
{
age = p.age;
}
};
void test01()
{
//括号法
Person p01; //Person p() 是错误的
Person p02(10);
Person p03(p2);
//显式法
Person p12 = Person(10);
Person p13 = Person(p12);
//Perspn(10):匿名对象,当这一行执行结束后会立即释放空间
//不能利用拷贝函数构造匿名对象,Person(p3) = Person p3;
//隐式转换法
Person p22 = 10;
person p23 = p22;
}
拷贝构造函数调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值返回方式返回局部对象
构造函数调用规则
- 只要创建一个类,C++就会创建3个函数默认构造(空实现),有参构造(空实现),拷贝构造(值拷贝)
- 若写了有参构造,则编译器不再提供默认构造函数,但会提供拷贝构造函数
- 若写了拷贝构造函数,则编译器不再提供其他普通构造函数
深拷贝与浅拷贝(面试常问)
- 浅拷贝:简单的复制拷贝
- 深拷贝:在堆区重新申请空间,进行拷贝
m_Heigt = new int(*p.m_Heigt);
初始化列表
用途:给类中的属性进行初始化
Person() :m_A(10),m_B(20) {}
Person(int a,int b,int c) :m_A(a),m_B(b),m_C(c) {}
类对象作为类成员
//Phone phone = pName Phone phone = Phone(pName)
Person(string sName,string pName):m_Name(sName), phone(pName) //好像只能用初始化列表??
{
}
string m_Name;
Phone phone;
静态成员
静态成员变量
- 所有对象公用一份数据
- 在编译阶段分配内存(在全局区)
- 在类内声明,类外初始化
class Person
{
public:
static int m_A;
private:
static int m_B; //静态成员变量同样有访问权限
};
//类内访问,类外声明
int Person::m_A = 10;
int Person::m_B = 100;
void test()
{
Person p;
cout << p.m_A; //静态成员变量不属于任何一个对象,因此可以 1.通过对象调用 2.通过类调用
cout << Person::m_B;
}
静态成员函数
- 所有对象公用一个函数
- 静态成员函数只能访问静态成员变量
class Person
{
static void func(){
cout << m_A;
cout << m_B; //错误,静态成员函数只能访问静态成员变量
}
static int m_A;
int m_B;
};
int Person::m_A = 10;
void test()
{
Person p;
cout << p.func(); //1.通过对象调用 2.通过类调用
cout << Person::func();
}
4.3 C++对象模型和this指针
成员变量和成员函数分开储存
- 只有非静态成员变量属于类的对象上
- 空对象占内存大小为1
this指针
- 解决名称冲突
- 返回对象本身用 *this
- this指针指向 被调用的成员函数 所属对象
class Person
{
public:
Person& addPerson(Person &p) //要返回对象的引用
{
this->age+= p.age;
return *this; //回对象本身用 *this
}
int age;
};
void test()
{
//链式调用规则
p2.addPerson(p1).addPerson(p1).addPerson(p1);
}
空指针访问成员函数
const修饰成员函数
-
常函数:在成员函数后加const修饰
- 常函数内不可以修改成员属性的值
- 成员属性声明时加 mutable修饰后,常函数就可以修改了
-
常对象:声明对象前加 const修饰
- 常对象只能调用常函数
class Person
{
public:
/*
this指针的本质相当于一个指针常量,Person * const this = p;
函数加const修饰后相当于 const Person * const this = p;
*/
void showPerson() const
{
//m_A = 100;
m_B = 100;
}
int m_A;
mutable int m_B;
};
4.4 友元
全局函数做友元
类做友元
成员函数做友元
class Building
{
friend void friendClass(Building &b); //用friend修饰全局函数就可以访问私有成员变量
private:
int age;
};
void friendClass(Building &b){}
4.5 运算符重载
4.6 继承
基本语法
class Son : public base1, public base2 ....
继承方式
继承中的对象模型
- 在父类中所有的非静态的成员属性都会被子类继承下去
- 父类中的私有属性也会被继承,但是不可以访问
继承同名成员的处理方式
- 访问子类同名变量,直接访问即可
- 访问父类同名变量,需要加作用域
- 若子类中出现了与父类中同名的成员函数,则子类中的函数会屏蔽中所有的同名成员函数
继承中同名的静态成员的处理方法
void test()
{
Son s;
cout << s.a; //通过调用对象的方式访问静态成员
cout << s.Base::a;
cout << Son::a; //通过类直接访问静态成员
cout << Son::Base::a;
}
- 访问子类同名静态变量,直接访问即可
- 访问父类同名静态变量,需要加作用域
- 若子类中出现了与父类中同名的静态成员函数,则子类中的函数会屏蔽中所有的同名静态函数
菱形继承
- 主要问题:在子类中继承了两份相同的数据,资源浪费,毫无意义
- 解决方法:虚继承
4.7 多态
多态的语法
-
多态的种类
静态多态:函数重载,运算符重载,复用函数名
动态多态:派生类和虚函数实现运行时多态
-
静态多态与动态多态的区别
静态多态地址早绑定,编译阶段确定函数地址
动态多态地址晚绑定,运行阶段确定函数地址
class Animal { public: virtual void doSpeak(){} }; //多态的条件 // 1. 有继承关系 // 2. 子类重写父类中的虚函数 class Cat : public Animal { public: void doSpeak(){} }; //多态的使用:父类的指针或引用 指向子类对象 void test(Animal &cat) //Animal &animal = cat; Animal *animal = new cat; { cat.doSpeak(); }
多态的原理
class AbstractCalculator
{
public:
int num1,num2;
virtual int getResult() //声明虚函数
{
return 0;
}
};
class addCaculator : public AbstractCalculator
{
public:
int getResult() //重写父类中的虚函数
{
return num1 + num2;
}
};
void test01()
{
AbstractCalculator *ab = new addCaculator;
ab->getResult(); //通过指针指向子类对象
}
void test02(abstraCalculator &ab) //通过引用指向子类对象
{
ab.getResult();
}
纯虚函数和抽象类
virtual void func() = 0;
//只要含有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也是抽象类
虚析构和纯虚析构
5 文件操作
- 文件的分类
- 文本文件
- 二进制文件
- 操作文件的三大类
- ofstream:写文件
- ifstream:写文件
- fstream:读写文件
5.1 文本文件
写文件
void test01()
{
fstream ofs; // 1. 创建对象
ofs.open("test.txt",ios::out); //打开文件
ofs << "输入想写入的内容" << endl;
ofs.close();
}
读文件
void test01()
{
ifstream ifs;
ifs.open("test.txt",ios::in);
if(!ifs.is_open())
{
cout << "文件打开失败" <<endl;
return;
}
string buf;
while(getline(ifs,buf)){
cout << buf << endl;
}
}