前言:由于考研需要以及C++强大的STL,决定入手c++!C语言部分就不做重复,直接开始核心编程部分
程序的内存模型
代码区
全局区
栈区:由计算机自己分配管理,由计算机进行分配和释放
堆区:由程序员进行分配(比如说用new进行数据开辟)
New操作符
在堆区进行开辟数据,由程序员进行手动开辟,释放利用操作符delete;
New返回的是该类型的指针;
Delete释放数组的时候要加括号,delete[] 数组指针;
#include<iostream>
using namespace std;
int main()
{
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 1;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
delete[] arr;
system("pause");
return 0;
}
引用:
作用:给变量起名;
语法:数据类型 &别名 =原名
Int &b = a;
#include<iostream>
using namespace std;
int main()
{
//引用数据类型
int a = 10;
int& b = a;
cout << a << endl;
cout << b << endl;
system("pause");
return 0;
}
引用必须要初始化;
Int &b //错误
引用一旦初始化,就不能更改
#include<iostream>
using namespace std;
int main()
{
//引用必须初始化
int a = 10;
int &b=a;
int c = 20;
b = c;
cout << a << endl;//由于a,b指向同一块内存,所以b=c之后相当于赋值更改了内存
cout << b << endl;
cout << c << endl;
system("pause");
return 0;
}
引用做函数参数
作用:函数传递时,可以利用引用让形参修饰实参
值传递
地址传递
引用传递
引用做函数返回值
作用:引用时可以作为函数的返回值存在的,
不要返回局部变量的引用
函数的调用可以作为左值
引用的本质在C++中实质上是一个指针常量。一旦引用后就不能发生改变。
常量引用
使用场景:修饰形参,防止误操作。
函数的提高
在C++中,函数中的参数可以设默认数,如果我们自己传入数据,就用自己的
数据,那么用默认值。
如果某个位置已经有了默认参数,那么从这个位置之后,从左到右都必须有参数。
如果函数声明有默认参数,函数实现就不能有默认参数
#include<iostream>
using namespace std;
int func(int a = 10, int b = 10);
int func(int a =10 , int b =10 )
{
return a + b;
}
int main()
{
system("pause");
return 0;
}
函数占位参数
C++函数的形参列表中可以有展位参数,用来做展位,调用函数时必须填补该位置。
语法:返回值类型 函数名(数据类型){}
#include<iostream>
using namespace std;
//占位参数
void func(int a,int)
{
cout << "你好" << endl;
}
int main()
{
func(10,10);
system("pause");
return 0;
}//目前用不到,但是后面课程会用到,
函数重载
作用:函数名相同,提高复用性
同一个作用域下
函数名相同
函数参数类型不同或者个数不同顺序不同
类与对象
封装、继承、多态
封装:
将属性和行为作为一个整体,表现生活的事务
将属性和行为加以权限控制
语法:
Class 类名(访问权限
#include<iostream>
using namespace std;
//设计一个圆类
//圆求周长的公式:
const double PI = 3.14;
class Circle
{
//访问权限
public:
//属性
//半径
int m_r;
double calculate()
{
return 2 * PI * m_r;
}
};
int main()
{
//创建一个对象
Circle c;
c.m_r = 10;
cout << "圆周长是" << c.calculate() << endl;
system("pause");
return 0;
}
封装:
Public:成员类内可以访问,类外可以访问;
Protected:成员类内可以访问,类外不可以访问,儿子可以访问父亲中的保护内容
Private:成员类内可以访问,类外不可以访问,儿子不可以访问父亲私有内容
成员属性设置为私有
优点:将所有成员属性设置为私有,可以自己控制读写权限;
对于写权限,我们可以检测数据的有效性
#include<iostream>
using namespace std;
//成员属性设置为私有
//对于写可以检测数据的有效性
class person
{
public:
//设置姓名
void setname(string name)
{
m_name = name;
}
//返回姓名
string getname()
{
return m_name;
}
private:
string m_name;
int m_age;
string m_lover;
};
int main()
{
person p;
p.setname("张三");
cout << p.getname() << endl;
system("pause");
return 0;
}
无论是基类还是子类,对象是不能访问protected成员的,而类的成员函数(基类或子类)都是可以访问protected成员的。protected成员在对于类对象来说访问权限和私有成员一样,但是对于子类来说,子类能访问基类保护成员,但不能访问基类私有成员。
对于private的一些属性,我们可以通过public成员函数等接口来访问private变量,
#include<iostream>
using namespace std;
class Cube
{
public:
void setL(int l)//设置长
{
m_L = l;
}
int getL()//获取长
{
return m_L;
}
void setw(int w)//设置宽
{
m_W = w;
}
int getw()//获取宽
{
return m_W;
}
void seth(int h)//设置高
{
m_h = h;
}
int geth()//获取高
{
return m_h;
}
//获取面积
int caculateS()
{
return 2 * m_L * m_W + 2 * m_W * m_h + 2 * m_L * m_h;
}
int caculateV()
{
return m_L * m_W * m_h;
}
private:
int m_L;//长
int m_W;//宽
int m_h;//高
};
//利用全局函数来判断是否相等
bool isSame(Cube &c, Cube &c1)
{
if (c.getL() == c1.getL() && c.getw() == c1.getw() && c.geth() ==c1.geth() )
return true;
}
int main()
{
Cube c;
c.setL(10);
c.setw(10);
c.seth(10);
cout << "c的面积是" << c.caculateS()<< endl;
cout << "c的体积是" << c.caculateV() << endl;
Cube c1;
c1.setL(10);
c1.setw(10);
c1.seth(10);
bool ret = isSame(c, c1);
if (ret)
cout << "两个是相等的" << endl;
else
cout << "两个不相等" << endl;
system("pause");
return 0;
}
对象的初始化和清理
构造函数与析构函数
构造函数:
• C++中的类需要定义与类名相同的特殊成员函数时,这种与类名相同的成员函数叫做构造函数;
• 构造函数可以在定义的时候有参数;
• 构造函数没有任何返回类型。
• 构造函数的调用: 一般情况下,C++编译器会自动的调用构造函数。特殊情况下,需要手工的调用构造函数。
class Test
{
public:
//构造函数
Test()
{
}
}
析构函数:
• C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的函数是析构函数;
• 析构函数没有参数和没有任何返回类型;
• 析构函数在对象销毁的时候自动调用;
• 析构函数调用机制: C++编译器自动调用。
class Test
{
~Test()
{
}
}
构造函数分类及调用
按参数分为有参构造和无参构造
按类型分普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
匿名对象:当对象执行结束之后,系统会立即回收掉匿名对象;
#include<iostream>
using namespace std;
class person
{
public:
person()//无参
{
cout << "构造函数的调用" << endl;
}
person(int a)//有参
{
int age = a;
cout << "构造函数调用" << endl;
}
//拷贝构造函数
person (const person& p)
{
int age = p.age;
}
int age;
};
void test()
{
person p1;
person p2 = person(10);
person p3 = person(p2);
person p4 = 10;
}
int main()
{
test();
system("pause");
return 0;
}
拷贝构造函数调用时机
使用一个已经创建完毕的对象来初始化一个对象
值传递的方式给函数参数传值
值方式返回局部对象
同类型的类对象是通过拷贝构造函数来完成整个复制过程的
构造函数的调用规则
如果我们写了有参构造,编译器就不再提供默认构造,依然提供拷贝构造;
如果我们写乱拷贝函数,那编译器就不提供其他构造函数,
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作;
深拷贝:在堆区重新申请空间,进行拷贝操作;
浅拷贝自身的问题就是堆区内存的重复释放;
#include<iostream>
using namespace std;
//构造 函数的调用规则
//创建一个类,c++编译器会给每个类添加至少3个函数
//默认函数
//析构函数
//拷贝函数
class person
{
public:
person()
{
cout << "默认函数调用" << endl;
}
person(int age ,int height)
{
cout << "person的有参构造函数" << endl;
m_age = age;
m_height = new int(height);
}
person(const person& p)
{
m_age = p.m_age;
//m_height = p.m_height;
//深拷贝
m_height = new int(*p.m_height);
cout << "拷贝函数调用" << endl;
}
~person()
{
if (m_height != NULL)
delete m_height;
m_height = NULL;
cout << "析构函数" << endl;
}
int m_age;//年龄
int *m_height;//身高
};
void test1()
{
person p(19,190);
person p2(p);
cout << "p2的年龄是" << p2.m_age << "身高为"<<*p2.m_height<<endl;
}
int main()
{
test1();
system("pause");
return 0;
}
初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)…{}
#include<iostream>
using namespace std;
class person
{
public:
/*person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}*/
person() :m_A(10), m_B(20), m_C(30)
{
}
int m_A;
int m_B;
int m_C;
};
/*void test01()
{
person p(10, 29, 90);
cout << p.m_A <<endl;
cout << p.m_B << endl;
cout << p.m_C << endl;
}*/
void test02()
{
person p;
cout << p.m_A << endl;
cout << p.m_B << endl;
cout << p.m_C << endl;
}
int main()
{
test02();
system("pause");
return 0;
}
类对象作为类成员
C++类中的成员考验是另一个类的对象,我们称该成员为成员对象
例如:
Class A()
Class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
#include<iostream>
#include<string.h>
using namespace std;
//类对象作为类成员
class phone
{
public:
phone(string pname)
{
cout << "phone 的构造函数调用" << endl;
m_pname = pname;
}
string m_pname;
};
class person
{
public:
person(string name, string pname) :m_name(name), m_phone(pname)
{
cout << "person的构造函数调用" << endl;
}
string m_name;
phone m_phone;
};
void test01()
{
person p("张三","苹果");
cout << p.m_name << "的手机是" << p.m_phone.m_pname<< endl;
}
int main()
{
test01();
system("pause");
return 0;
}
由图可知:当其他类对象作为本类对象成员,构造时候先构造对象,再构造自身,析构函数则相反
静态成员函数:
所有对象共享同一个函数
静态成员函数只能访问静态成员变量;
静态数据成员只能在类体外初始化;
不能在类内初始化或通过构造函数或初始化列表初始化静态数据成员;
静态数据成员可以通过对象名引用,也可以通过类名来引用;
公共的静态数据成员可以在类外直接引用,也可以通过对象名引用,但私有的静态数据成员只能公用的成员函数引用。
由于没有this指针,由此决定了静态成员函数不能直接访问类的非静态数据成员;
静态成员函数主要用来访问静态数据成员,而不访问非静态成员;
如果一定要访问本类的非静态成员,应该加对象名和成员运算符"."。
C++对象模型和this指针
成员变量和成员函数的分开储存
只有非静态成员变量才属于类的对象
#include<iostream>
using namespace std;
class person
{
int m_A;//非静态成员变量,属于类的对象上
static int m_B;//静态成员函数,不属于类对象上
void func()
{
}//非静态函数和成员函数是分开存储的。不属于类的对象
static void func2()//静态成员函数,不属于类的对象上;
{
}
};
int person::m_B = 0;
void test()
{
person p;
//空对象的占用内存为1;
//C++编译器会给每个空对象分配一个字节的空间,是为了区分空对象占的位置
//每个空对象也应该有一个独一味二的内存地址;
cout << "size of p =" << sizeof(p) << endl;
}
int main()
{
test();
system("pause");
return 0;
}
This 指针的概念
This指针指向被调用的成员函数所属的对象;
#include<iostream>
using namespace std;
class person
{
public:
person(int age)
{
this->age = age;
}
int age;
};
void test()
{
person p(10);
cout << "此人的年龄是" << p.age << endl;
}
int main()
{
test();
system("pause");
return 0;
}
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有this指针,如果用到this指针,需要加以判断保证代码的健壮性。
#include<iostream>
using namespace std;
class person
{
public:
void showclassname()
{
cout << "this is class name" << endl;
}
void showpersonage()
{
if (this == NULL)
{
return;//这是之后添加的代码
}
cout << "age =" << this->m_age << endl;
}//报错原因是因为传入的指针是为空,NULL;
int m_age;
};
void test01()
{
person* p = NULL;
p->showpersonage();
}
int main()
{
test01();
system("pause");
return 0;
}
Const修饰成员函数(只读形式)
常函数
成员函数后加const后我们称这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,再常函数中依然可以修改
常对象
声明对象前加const称该对象为常对象
常对象只能调用常函数
友元
关键字:friend
#include<iostream>
using namespace std;
class person
{
public:
//通过成员函数进行重载
/*
person operator+(person& p)
{
person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
*/
int m_a;
int m_b;
};
//通过全局函数进行重载
person operator+(person& p1, person p2)
{
person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
void test()
{
person p1;
p1.m_a = 10;
p1.m_b = 30;
person p2;
p2.m_a = 20;
p2.m_b = 10;
person p3 = p1 + p2;
cout << "p3.m_a = " << p3.m_a << endl;
cout << "p3.m_b = " << p3.m_b << endl;
}
int main()
{
test();
system("pause");
return 0;
}
运算符重载
对已有的运算符进行重新的定义,赋予去另一种功能,以适应不同的数据类型
例子:
加号运算符重载:
通过成员函数进行重载
通过全局函数进行重载
左移运算符
<<
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
递增运算符重载
继承
继承是面向对象的三大特征之一
继承的基本语法
class 子类 :继承方法,父类(基类)
减少重复的代码。
继承的三种继承
共有继承
保护继承
私有继承
#include<iostream>
using namespace std;
//公共继承
class base1
{
public :
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son :public base1
{
public:
void func()
{
m_a = 10;//父类中的公共权限成员,到子类中依然是公共权限
m_b = 10;//父类中的保护权限成员,到子类中依然是保护权限
//m_c = 10;//父类中的私有权限成员,到子类中不能访问;
}
};
class son1 :protected base1
{
void func()
{
m_a = 10;
m_b = 10;
//m_c = 10;//私有权限不能访问
}
};
class son2 :private base1
{
void func()
{
m_a = 10;//父类中的公共权限成员变为私有成员
m_b = 10;//父类中的保护权限成员变为私有成员
//m_c = 10;//父类中私有成员不能访问;
}
};
class son3 :private son2
{
void fun()
{
//m_a = 100;//父类中已经是私有成员不能访问
//m_b = 100;//父类中已经是私有成员不能访问
}
};
int main()
{
system("pause");
return 0;
}
继承中的对象模型
父类中所有非静态属性都会被子类继承下去
父类中的私有私有属性被编译器隐藏了,所以访问不到,但是确实被继承下去了;
//利用开发人员提示工具查看对象模型
//陶砖盘符F:
//跳转文件路径cd具体 路径如下
查看命名:
reportSingleClassLayout 类名 文件名
继承中构造和析构顺序
子列继承父类以后
#include<iostream>
using namespace std;
//继承中的对象模型
class base
{
public :
base()
{
cout << "base的构造函数" << endl;
}
~base()
{
cout << "base的析构函数" << endl;
}
};
class son :public base
{
public:
son()
{
cout << "son的构造函数" << endl;
}
~son()
{
cout << "son的析构函数" << endl;
}
};
void test()
{
son a;
}
int main()
{
test();
system("pause");
return 0;
}
继承中的构造和析构顺序如下;
先构造父类,再构造子类,析构的顺序与构造的顺序相反
#include<iostream>
using namespace std;
//继承中的对象模型
class base
{
public:
base()
{
m_a = 100;
}
void func()
{
cout << "base_func()调用" << endl;
}
int m_a;
};
class son :public base
{
public:
son()
{
m_a = 200;
}
void func()
{
cout << "son_func()调用" << endl;
}
int m_a;
};
void test()
{
son s;
cout << "son中的m_a= " << s.m_a << endl;
//如果通过子类的对象访问到父类中的同名函数,需要加作用域
cout << "base下m_a=" << s.base::m_a << endl;
}
void test1()
{
son s;
s.func();
s.base::func();//同名函数的调用也是需要加上作用域的
}
int main()
{
test1();
system("pause");
return 0;
}
/如果子类中出现和弗列同名的成员函数,子类的同名函数会隐藏父类中所有的同名函数
//如果向访问到父类中被隐藏的同名成员函数,需要加作用域;
继承同名静态成员处理方式
同名的静态成员处理吧方式和非静态处理方式一样,只不过两种访问的方式(通过对象访问和通过类名访问)
#include<iostream>
using namespace std;
class base
{
public:
static int m_a;
};
int base::m_a = 100;
class son : public base
{
public:
static int m_a;
};
int son::m_a = 200;
void test1()
{
//通过对象去访问
son s;
cout << "m_a=" << s.m_a << endl;
cout << "base的m_a =" << s.base::m_a << endl;
cout << "通过类名访问" << endl;
//通过类名去访问
cout << "son下的m_a=" << son::m_a << endl;
cout << "base下的m_a=" << son::base::m_a << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
多继承语法
c++允许一个类继承多个类
语法:class 子类 :继承方式 父类1,继承方式 父类2…
c++中实际上不建议用多继承;
多继承中如果父类出现了同名情况 ,子类使用时候要加作用域;
菱形继承
菱形继承概念:
两个派生类继承同一个基类,又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
菱形继承问题
二义性
vbptr
虚基类指针
多态
三大特征之一
动态多态的满足条件
一、有继承关系
二、子类重写父类的虚函数
静态多态的函数地址早绑定-编译阶段确定函数地址
动态函数的函数地址晚绑定-运行阶段确定函数地址
#include<iostream>
using namespace std;
//多态
class annimal//动物类
{
public:
virtual void speak()//虚函数
{
cout << "动物在说话" << endl;
}
};
class cat : public annimal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class dog :public annimal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,
//地址晚绑定
void dospeak(annimal& animal)
{
animal.speak();
}
void test()
{
cat ca;
dospeak(ca);
}
void test1()
{
dog d;
dospeak(d);
}
int main()
{
test1();
system("pause");
return 0;
}
//动态多态的满足条件
有继承关系
重写父类中的虚函数
区别于重写和重载
重写:函数返回值类型,函数名,参数列表完全一致称为重写
纯虚函数和抽象类
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)=0;
当类中有纯虚函数,这个类也称为抽象类
抽象类特点:
无法实例化对象
子列必须重写抽象类的纯虚函数,否则 也属于抽象类;
纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
纯虚函数是一定要被继承的,否则它存在没有任何意义。
#include<iostream>
using namespace std;
//抽象类无法实例化对象
//抽象类的子类必须抽血父类中的纯虚函数
class base
{
public:
virtual void func() = 0;//纯虚函数,只要有一个虚函数,那么这个类就是抽象类
};
class son : public base
{
public:
virtual void func()//抽象函数必须重写
{
}
};
void test()
{
//base b//抽象类无法实例化对象
//new base //抽象类无法实例化对象
base* base = new son;
base->func();
}
int main()
{
test();
system("pause");
return 0;
}
实例
#include<iostream>
using namespace std;
//多态案例
class abstractdrinking
{
public:
virtual void boil() = 0;
virtual void brew() = 0;
virtual void pourinput() = 0;
virtual void putsomething() = 0;
void makedrink()
{
boil();
brew();
pourinput();
putsomething();
}
};
//制作咖啡
class coffee : public abstractdrinking
{
public:
virtual void boil()
{
cout << "煮开水" << endl;
}
virtual void brew()
{
cout << "搅拌" << endl;
}
virtual void pourinput()
{
cout << "倒入开水" << endl;
}
virtual void putsomething()
{
cout << "倒入咖啡粉" << endl;
}
};
class tea : public abstractdrinking
{
public:
virtual void boil()
{
cout << "煮开水" << endl;
}
virtual void brew()
{
cout << "搅拌" << endl;
}
virtual void pourinput()
{
cout << "导入开水" << endl;
}
virtual void putsomething()
{
cout << "倒入茶叶" << endl;
}
};
void dowork(abstractdrinking *abs)abstractdrinking *abs =new coffee
{
abs->makedrink();
delete abs;
}
void test()
{
dowork(new coffee);
}
int main()
{
test();
system("pause");
return 0;
}
文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束就会被释放
通过文件可以将数据持久化
C++对文件操作需要包含头文件
文件类型分为两种
一、文本文件:文件以文本的ascll码的形式存储在计算机中
二、二进制文件、文件按以文本的二进制的形式存储在计算机中,用户一般不能直接直接读懂他们
操作文件的三大类
1、ofstream:写操作
2、ifstream:读文件
3、fstream:读写文件
文本文件
写文件步骤
包含头文件
#include
创建流对象
ofstream ofs;
打开文件
ofs.open("文件路径,打开方式“);
写数据
ofs<<"写入的数据”;
关闭文件
ofs.close();
文件的打开方式
文件的打开方式可以配合使用,利用|操作符
#include<iostream>
using namespace std;
#include<fstream>
//文本文件,头文件
void test()
{
ofstream ofs;
ofs.open("test.txt", ios::out);//指定打开的方式
ofs << "姓名:张三" << endl;
ofs << "年龄:20" << endl;
ofs << "学历:大学" << endl;
ofs << "性别:年龄" << endl;
ofs.close();
}
int main()
{
test();
system("pause");
return 0;
}
如果不写明路径,那么默认路径就是代码的所在路径的文件夹中
二进制文件
以二进制的方式对文件进行读写操作
打开方式为ios::binary
写文件
二进制方式写文件