C++学习总结
学习资源:https://www.bilibili.com/video/BV1QE41147RT?p=1
入门
C++程序开发过程
1 算法设计
2源程序编辑
3编译
4连接
5运行调试
重视文档,程序=代码+文档
C++支持面向对象的观点和方法。面向对象把客观事物看作对象,对象间通过消息传送
面向对象的基本概念:
封装:隐蔽细节,对外形成边界,只保留有限的对外接口,使用方便、安全。
继承:改造、扩展已有类
多态:同样的信息作用在不同对象上有可能引起不同的效果。
代码的运行过程:
高级语言程序翻译机器语言程序。
三种翻译程序:
汇编程序:汇编语言翻译成目标程序
编译程序:高级语言翻译成目标程序
解释程序:高级语言源程序翻译成机器指令、边翻译边执行。
如何表示负数:用补码
定点与浮点
浮点数:小数点浮动
简单程序设计
C++输入与输出可以调用预定义的功能模块实现。
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!"<< endl ; //cout是输出输出流类库的一个标准输出流对象,向显示器输数据。
cout << "C++!" << endl;
return 0;
}
什么是输出流?
在C++看来,数据的输出就是信息的流动,输出流就是程序的空间到外部的设备的一个信息流通的通道,所以要输出就需要建立这么一个通道。向显示器的输出通道是默认打开好的,cout就是向流送数据的对象,这个动作则是依靠<<
(插入运算符)实现。endl
表示行结束。
为了解决重名的困扰,使用命名空间std.
C++常量可以写后缀。
定义变量,对变量初始化是好习惯。
后两种是列表初始化。
C++的特殊运算符
自增(i++、++i)与 自减(j–、--j)。前置先运算、后操作其他。后置相反。
C++没有赋值语句,只有赋值表达式
赋值语句是由赋值表达式再加上分号构成的表达式语句。 其一般形式为: 变量=表达式;
赋值语句的功能和特点都与赋值表达式相同。
赋值表达式:a=b
赋值语句:a=b;
例如 x=a>b?a:b
a>b==True,则x=a,否则x=b
sizeof运算:计算对象字节数
右移相当于除于2,左移相当于乘以2,左移可能导致溢出。
数据类型转换
混合运算时可以使用强制类型转换。
输入输出流
标准的输出流大多数情况指向屏幕,标准的输入流大多数情况下指向键盘。
操作符用来控制输出输入的格式。
if 、switch、while
switch用法
break使得能执行后跳出这个循环,否则会继续执行下去。
#include <iostream>
using namespace std;
int main()
{
int i = 1, sum = 0;
while (i <= 10)
{
sum += i;
i++;
}
cout << "sum=" << sum << endl;
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int i = 1, sum = 0;
do
{
sum += i;
i++;
}
while (i <= 10);
cout << "sum=" << sum << endl;
return 0;
}
FOR
#include <iostream>
using namespace std;
int main()
{
int n;
cout << "Enter:";
cin >> n;
cout << "Number" << n << "Factors";
for (int k = 1; k <= n; k++)
if (n % k == 0)
cout << k << " ";
cout << endl;
return 0;
}
自定义类型
枚举类型
案例:
#include <iostream>
using namespace std;
enum GameResult {WIN,LOSE,TIE,CANCEL};
int main()
{
GameResult result;
enum GameResult omit = CANCEL;
for (int count = WIN; count <= CANCEL; count++)
{
result = GameResult(count);
if (result == omit)
cout << "The game was cancelled" << endl;
else {
cout << "The game was played";
if (result == WIN) cout << "and we won!";
if (result == LOSE)cout << "and we lost";
cout << endl;
}
}
return 0;
}
注意:
- c++ 中声明完枚举类型后,声明变量时,可以不写关键字 enum
- 枚举类型的数据可以和整型数据相互转换,枚举型数据可以隐含转换为整型数据
例如:int count=WIN
就是把枚举类型的数据 WIN 隐含转换为整型,作为整型变量 count 的初值。
例题
#include <iostream>
using namespace std;
const float PI = 3.1416;
int main()
{
int iType;
float radius, a, b, area;
cout << "图形的类型为(1-圆形,2-长方形):";
cin >> iType;
switch (iType)
{
case 1:
cout << "圆的半径:";
cin >> radius;
area = PI * radius * radius;
cout << "面积为:" << area << endl;
break;
case 2:
cout << "矩形的长为:";
cin >> a;
cout << "矩形的宽为:";
cin >> b;
area = a * b;
cout << "面积为:" << area << endl;
break;
default:
cout << "非法输入" << endl;
}
return 0;
}
例题
#include <iostream>
using namespace std;
struct MyTimeStruct
{
unsigned int year;
unsigned int month;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main()
{
MyTimeStruct myTime = { 2021,2,34,14,52 };
cout << "please input year:"<<endl;
cin >> myTime.year;
cout << "please input month:" << endl;
cin >> myTime.month;
cout << "please input day:" << endl;
cin >> myTime.day;
cout << "please input hour:" << endl;
cin >> myTime.hour;
cout << "please input min:" << endl;
cin >> myTime.min;
cout << "please input sec:" << endl;
cin >> myTime.sec;
cout << "the time is set to:" << myTime.year << "/"
<< myTime.month << "/"
<< myTime.day << " "
<< myTime.hour << ":"
<< myTime.min << ":"
<< myTime.sec << endl;
return 0;
}
函数
要点:函数的定义和调用、内联函数、constexpr函数、函数重载、
函数定义和调用
形参:不占内存空间,调用时才占空间。
调用函数需要先声明函数原型。
实参:占用空间的变量。
简单案例:
//计算x的n次幂
#include <iostream>
using namespace std;
double power(double x, int n)
{
double val = 1.0;
while (n--)
val *= x;
return val;
}
int main()
{
double pow;
pow = power(5, 2);
cout << "5*5=" << pow << endl;
return 0;
}
案例2:二进制转10进制
#include <iostream>
using namespace std;
double power(double x, int n)
int main()
{
int value=0;
cout<<"Enter an 8 bit binary number:";
for (int i=7;i>=0;i--){
char ch;
cin>>ch;
if(ch=='1')
value+=static_cast
}
}
double power(double x, int n)
{
double val = 1.0;
while (n--)
val *= x;
return val;
}
实例3:计算
π
\pi
π
#include <iostream>
using namespace std;
//计算x的n次方
double arctan(double x)
{
double sqr = x * x;
double e = x;
double r = 0;
int i = 1;
while (e / i > 1e-15)
{
double f = e / i;
r = (i % 4 == 1) ? r + f : r - f;
e = e * sqr;
i += 2;
}
return r;
}
int main()
{
double a = 16.0 * arctan(1 / 5.0);
double b = 4.0 * arctan(1 / 239.0);
cout << "PI =" << a-b << endl;
return 0;
}
例题:汉诺塔
#include <iostream>
using namespace std;
//src最上面的一个移到dest
void move(char src, char dest) {
cout << src << "-->" << dest << endl;
}
//从src移到dest,以medium为媒介
void hanoi(int n, char src, char medium, char dest) {
if (n == 1)
move(src, dest);
else {
hanoi(n - 1, src, dest, medium);
move(src, dest);
hanoi(n - 1, medium, src, dest);
}
}
int main()
{
int m;
cout << "Enter the number of diskes:";
cin >> m;
cout << "the steps to moving" << m << "diskes:" << endl;
hanoi(m, 'A', 'B', 'C');
return 0;
}
实参与形参
实参与形参类型不符,则编译器会先尝试做类型转换。如果不能转换,编译器报错。
引用
在函数未被调用时,函数的形参并不占有实际的内存空间,也没有实际的值。只有在函数被调用时才为形参分配存储单元,并将实参与形参结合。函数的参数传递指的就是形参与实参结合(简称形实结合)的过程,形实结合的方式有值传递和引用传递。
值传递:指当发生函数调用时,给形参分配内存空间,并用实参来初始化形参(直接将实参的值传递给形参)。这一过程是参数值的单向传递过程,一旦形参获得了值便与实参脱离关系,此后无论形参发生了怎样的改变,都不会影响到实参。
那么如何更改形参从而调整实参呢?需要使用引用。
内联函数
调用函数产生的时间和空间开销会降低代码的运行效率,因此对于一些简单函数的调用,可以使用内联函数
内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。这样就节省了参数传递、控制转移等开销。
代码示例:
内联函数的定义与普通函数的定义方式几乎一样,只是需要使用关键宇 inline
include <iostream>
using namespace std;
const double PI=3.14159265358979;
//内联函数,根据圆的半径计算其面积
inline double calArea(double radius){
return PI*radius*radius
}
int main() {
double r = 3.0; //r是圆的半径
//调用内联函数求圆的面积,编译时此处被替换为CalArea函数体语句
double area = calArea(r);
cout << area << endl;
return 0;
}
常量表达式
constexpr函数语法规定,返回值一定是一个常量表达式。
函数默认值
重载函数
C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。方便使用,便于记忆。
合法的重载函数,要求形参个数不同或者类型不同。
两个以上的函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载。
案例:
//形参类型不同
int add (int x,int y);
float add(float x,float y)
//形参个数不同
int add(int x,int y);
int add(int x,int y,int z)
系统函数
C++的系统库中提供了几百个函数可供程序员使用。例如:求平方根函数 (sqrt) 、求绝对值函数 (abs) 等。要调用,就嵌入头文件。
例题
面向对象
抽象、封装、继承、多态
时钟案例:
继承:在已有类的基础上进行扩展。形成新类。
类和对象
1 如何定义一个类:
公有成员:类与外部的接口
私有成员:只允许本类函数访问,类外部的函数都不能访问。紧跟在类名后面声明的私有成员,private可省略。
保护成员:与private相似,区别在继承与派生时对派生类的影响不同。
2 对象定义
类中成员直接使用成员名访问。类外使用“对象名.成员名”方式访问public成员。
成员函数
类的成员函数描述的是类的行为,例如时钟类的成员函数 setTime() showTime() ,成员函数是程序算法的实现部分,是对封装的数据进行操作的方法。
案例:
void Clock::setTime(int newH,int newM,int newS){
hour=newH;
minute=newM;
second=newS;
}
与普通函数不同,类的成员函数名需要用类名来限制,例如"Clock: : ShowTime
"
调用成员函数:myclock.ShowTime()
类的成员函数也可以有默认形参值,类成员函数的默认值,一定要写在类定义中,而不能写在类定义之外的函数实现中。
class Clock{
public:
void setTime(int newH=0,int newM=0,int newS=0);
}
内联成员函数 :成员函数也能内联,提高执行效率,且有两种声明方式。
隐式声明:将函数体直接放在类体内,这种方法称之为隐式声明,例如下面的showTime函数便是内联函数。
class Clock{
public:
void setTime(int newH=0,int newM=0,int newS=0);
void showTime(){
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
prviate:
int hour,minute,second;
}
显示声明:为了保证类定义的简洁,可以采用关键字 inline 显式声明的方式。即在函数体实现
时,在函数返回值类型前加上 inline ,类定义中不加入 showTime 的函数体。
inline void Clock:: showTime(){
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
构造函数
就像定义基本类型变量时可以同时进行初始化一样,在定义对象的时候,也可以同时对它的数据成员赋初值。在定义对象的时候进行的数据成员设置,称为对象的初始化。在特定对象使用结束时,还需要进行一些清理工作。C++ 程序中的初始化和清理工作,分别由两个特殊的成员函数来完成,它们就是构造函数和析构函数。
构造函数:将对象初始化一个特定的初始状态,语法规定必须要有构造函数
构造函数是对象创建时被自动调用的。
没有实参的构造函数称为默认构造函数。
class Clock
{
public:
Clock(int newH, int newM, int newS);//构造函数声明
Clock();//默认构造函数
void setTime(int newH = 0, int newM = 0, int newS = 0);
void showTime();
private:
int hour, minute, second;
};
//默认构造函数实现
Clock::Clock():hour(0),minute(0),second(0){}
//构造函数实现
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS){
}
委托构造函数
为了避免相同功能的构造函数被重复编写的麻烦,出现了委托构造函数。
例如,clock的两类构造函数
//构造函数实现
Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS){
}
//默认构造函数实现
Clock::Clock():hour(0),minute(0),second(0){}
默认构造函数可以委托构造函数
//构造函数实现
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS){
}
//默认构造函数实现
Clock():Clock(0,0,0){}
保证代码实现的一致性。
复制构造函数
用已经存在的对象初始化新对象。
编译器会定义默认的复制构造函数。
三种情况案例
//4_2.cpp
#include <iostream>
using namespace std;
class Point { //Point 类的定义
public: //外部接口
Point(int xx = 0, int yy = 0) { //构造函数
x = xx;
y = yy;
}
Point(Point &p); //复制构造函数
int getX() {
return x;
}
int getY() {
return y;
}
private: //私有数据
int x, y;
};
//复制构造函数的实现
Point::Point(Point &p) {
x = p.x;
y = p.y;
cout << "Calling the copy constructor" << endl;
}
//形参为Point类对象的函数
void fun1(Point p) {
cout << p.getX() << endl;
}
//返回值为Point类对象的函数
Point fun2() {
Point a(1, 2);
return a;
}
//主程序
int main() {
Point a(4, 5); //第一个对象A
Point b = a; //情况一,用A初始化B。第一次调用拷贝构造函数
cout << b.getX() << endl;
fun1(b); //情况二,对象B作为fun1的实参。第二次调用拷贝构造函数
b = fun2(); //情况三,函数的返回值是类对象,函数返回时,调用拷贝构造函数
cout << b.getX() << endl;
return 0;
}
析构函数
简单来说,析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一
些清理工作,也就是专门做扫尾工作的。
析构函数是在对象的生存期即将结束的时刻被自动调用的。它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
与构造函数一样,析构画数通常也是类的一个公有函数成员,它的名称是由类名前面加"~"构成,没有返回值。
和构造函数不同的是析掏函数不接收任何参数,但可以是虚函数。如果不进行显式说明,系统也会生成一个函数体为空的隐含析构函数。
class Clock
{
public:
Clock(int newH, int newM, int newS);//构造函数声明
Clock();//默认构造函数
void setTime(int newH = 0, int newM = 0, int newS = 0);
void showTime();
~Clock() ;
private:
int hour, minute, second;
};
如果不进行显式说明,系统也会生成一个函数体为空的隐含析构函数。
前向引用申明
一般来说,类是先定义再调用的,但可能出现是在处理相对复杂的问题、考虑类的组合时,很可能遇到两个类相互引用的情况,这种情况也称为循环依赖。
由于在使用一个类之前,必须首先定义该类,因此无论将哪一个类的定义放在前面,都会引起编译错误。解决这种问题的办法,就是使用前向引用声明。
前向引用声明,是在引用未定义的类之前,将该类的名字告诉编译器,使编译器知道那是一个类名。这样,当程序中使用这个类名时,编译器就不会认为是错误,而类的完整定义可以在程序的其他地方。在上述程序加上下面的前向引用声明,问题就解决了。
class B; //前向引用声明
class A{ //A类的定义
public: //外部接口
void f(B b); //B 类对象 为形参的成员函数
};
class B{ //B类的定义
public: //外部接口
void g(A a); //以A类对象a为形参的成员函数
};
使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。
class Fred; //前向引用声明
class Barney{
Fred x; //错误:类 Fred 的定义尚不完善
};
class Fred{
Barney y;
};
对于这段程序,编译器将指出错误。原因是对类名 Fred 的前向引用声明只能说明
Fred 是一个类名,而不能给出该类的完整定义,因此在类 Barney 中就不能定义类 Fred的数据成员。因此使两个类以彼此的对象为数据成员,是不合法的。
UML
UML 语言是一种典型的面向对象建模语言,而不是→种编程语言,在 UML 语言中
用符号描述概念,概念间的关系描述为连接符号的线。
类和对象
类图中最基本的是要图形化描述类,要表示类的名称、数据成员和函数成员,以及各
成员的访问控制属性。
UML 语言中,用一个由上到下分为 段的矩形来表示一个类。类名写在顶部区域,数据成员(数据, UML 中称为属性)在中间区域,函数成员(行为, UML 中称为操作)出现在底部区域。当然,也可以看作是用 个矩形上下相叠,分别表示类的名称、属性和操作。而且,除了名称这个部分外,其他两个部分是可选的,即类的属性和操作可以不表示出来,也就是说,一个写了类名的矩形就可以代表一个类。
还可以用来表示类之间的关系,例如依赖和关联
结构体、联合体、
结构体是一种特殊形态的类,它和类一样,可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限,可以继承,支持包含多态等,二者定义的语法形式也几乎→样。结构体和类的唯一区别在于,结构体和类具有不同的默认访问控制属性:在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型(private) ;在结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为公有类(public) 。
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
struct Student { //学生信息结构体
int num; //学号
string name; //姓名
char sex; //性别
int age; //年龄
};
int main() {
Student stu = { 97001, "Lin Lin", 'F', 19 };
cout << "Num: " << stu.num << endl;
cout << "Name: " << stu.name << endl;
cout << "Sex: " << stu.sex << endl;
cout << "Age: " << stu.age << endl;
return 0;
}
联合体是一种特殊形态的类,它可以有自己的数据成员和函数成员,可以有自己的构
造函数和析构函数,可以控制访问权限。与结构体一样,联合体也是从C语言继承而来
的,因此它的默认访问控制属性也是公共类型的。联合体的全部数据成员共享同一组内
存单元。
案例:使用联合体保存成绩信息,并且输出。
#include <string>
#include <iostream>
using namespace std;
class ExamInfo {
public:
//三种构造函数,分别用等级、是否通过和百分来初始化
ExamInfo(string name, char grade)
: name(name), mode(GRADE), grade(grade) { }
ExamInfo(string name, bool pass)
: name(name), mode(PASS), pass(pass) { }
ExamInfo(string name, int percent)
: name(name), mode(PERCENTAGE), percent(percent) { }
void show();
private:
string name; //课程名称
enum {
GRADE,
PASS,
PERCENTAGE
} mode; //采用何种计分方式
union {
char grade; //等级制的成绩
bool pass; //是否通过
int percent; //百分制的成绩
};
};
void ExamInfo::show() {
cout << name << ": ";
switch (mode) {
case GRADE:
cout << grade;
break;
case PASS:
cout << (pass ? "PASS" : "FAIL");
break;
case PERCENTAGE:
cout << percent;
break;
}
cout << endl;
}
int main() {
ExamInfo course1("English", 'B');
ExamInfo course2("Calculus", true);
ExamInfo course3("C++ Programming", 85);
course1.show();
course2.show();
course3.show();
return 0;
}
例题
# include<iostream>
using namespace std;
enum CPU_Rank{P1=1,P2,P3,P4,P5,P6,P7}; //声明枚举类型
class CPU
{
private:
CPU_Rank rank;
int frequency;
float voltage;
public:
CPU(CPU_Rank r,int f,float v)//构造函数
{
rank = r;
frequency = f;
voltage = v;
cout << "构造了一个CPU!" << endl;
}
~CPU()
{
cout << "构造析构函数" << endl;
}
CPU_Rank GetRank() const { return rank; }
int GetFrequency() const { return frequency; }
float GetVoltage() const { return voltage;}
void SetRank(CPU_Rank r) { rank = r; }
void SetFrequency(int f) { frequency = f; }
void SetVoltage(float v) { voltage = v; }
void Run() { cout << "CPU开始运行!" << endl; }
void Stop() { cout << "CPU停止运行!" << endl; }
};
enum RAM_Type{DDR2=2,DDR3,DDR4};
class RAM
{
private:
enum RAM_Type type;
unsigned int frequency;//MHz
unsigned int size;//GB
public:
RAM(RAM_Type t, unsigned int f, unsigned int s)
{
type = t;
frequency = f;
size = s;
cout << "构造了一个RAM!" << endl;
}
~RAM() { cout << "析构了一个RAM" << endl; }
RAM_Type GetType() const { return type; }
unsigned int GetFrequency() const { return frequency; }
unsigned int GetSize() const { return size; }
void SetType(RAM_Type t) { type = t;}
void SetFrequency(unsigned int f) { frequency = f;}
void SetSize(unsigned int s) { size = s; }
void Run() { cout << "RAM开始运行!" << endl; }
void Stop() { cout << "RAM停止运行" << endl; }
};
enum CDROM_Interface{SATA,USB};
enum CDROM_Install_type{external,built_in};
class CD_ROM
{
private:
enum CDROM_Interface interface_type;
unsigned int cache_size;//MB
CDROM_Install_type install_type;
public:
CD_ROM(CDROM_Interface i, unsigned int s, CDROM_Install_type it)
{
interface_type = i;
cache_size = s;
install_type = it;
cout << "构造了一个CD_ROM!" << endl;
}
~CD_ROM() { cout << "析构了一个CD_ROM!" << endl; }
CDROM_Interface GetInterfaceType() const { return interface_type; }
unsigned int GetSize() const { return cache_size; }
CDROM_Install_type GetInstallType() const { return install_type; }
void SetInterfaceType(CDROM_Interface i) { interface_type = i; }
void SetSize(unsigned int s) { cache_size = s; }
void SetInstallType(CDROM_Install_type it) { install_type = it; }
void Run() { cout << "CD_ROM开始运行!" << endl; }
void Stop() { cout << "CD_ROM 停止运行" << endl; }
};
class COMPUTER
{
private:
CPU my_cpu;
RAM my_ram;
CD_ROM my_cdrom;
unsigned int storage_size;//GB
unsigned int bandwidth;//MB
public:
COMPUTER(CPU c, RAM r, CD_ROM cd, unsigned int s, unsigned int b);
~COMPUTER() { cout << "析构了一个COMPUTER" << endl; }
void Run()
{
my_cpu.Run();
my_ram.Run();
my_cdrom.Run();
cout << "COMPUTER 开始运行" << endl;
}
void Stop()
{
my_cpu.Stop();
my_ram.Stop();
my_cdrom.Stop();
cout << "COMPUTER 开始运行" << endl;
}
};
COMPUTER::COMPUTER(CPU c, RAM r, CD_ROM cd, unsigned int s, unsigned int b) :my_cpu(c), my_ram(r), my_cdrom(cd)
{
storage_size = s;
bandwidth = b;
cout << "构造了一个COMPUTER" << endl;
}
int main()
{
CPU a{ P6,300,2.8 };
a.Run();
a.Stop();
cout << "***********\n" << endl;
RAM b{ DDR3,1600,8 };
b.Run();
b.Stop();
cout << "***********\n" << endl;
CD_ROM c(SATA, 2, built_in);
c.Run();
c.Stop();
cout << "***********\n" << endl;
COMPUTER my_computer(a, b, c, 128, 10);
cout << "*************\n";
my_computer.Run();
my_computer.Stop();
cout << "*************\n";
return 0;
}