目录
- 类型转换
- 自定义类型
- 类型别名:为已有的类型另外命名
- 枚举类型
- auto类型
- decltype类型
- 函数的参数传递
- 含有可变个数参数的函数
- 内联函数
- 带默认参数值的函数
- 函数重载
- 类与对象
- 类和对象的定义
- 构造函数
- 委托构造函数
- 复制构造函数
- 析构函数
- 类的组合
- 前向引用声明
- 结构体
- 联合体
- 枚举类
- 数据的共享与保护
- 标识符的作用域和可见性
- 对象的生存期、
- 类的静态数据成员
- 类的友元
- 共享数据的保护
- 多文件结构和编译预处理命令
- 数组
- 数组的定义与使用
- 数组的存储和初始化
- 对象数组
- 基于范围的FOR循环
- 指针
- 指针的概念、定义和指针运算
- 指针的初始化和赋值
- 指针的算术运算、关系运算
类型转换
- 显式类型转换
- 语法形式:
- 类型说明符(表达式)
- (类型说明符)表达式
- 类型转换操作符<类型说明符>(表达式)
- const_cast dynamic_cast reinterpret_cast static_cast(用的多)
- 语法形式:
- 例子(等价写法)
int(z);
(int)z;
static_cast(z);
自定义类型
类型别名:为已有的类型另外命名
-
使用typedef
- typedef 已有类型名 新类型名表
typedef double Area, Volume; typedef int Natural; Natural i1,i2; Area a; Volume v;
-
使用using
- using 新类型名 = 已有类型名
using Area = double; using Volume = double;
枚举类型
- 语法形式:enum 枚举类型名 {变量值列表}
- 默认情况下:SUM=0,MON=1,TUE=2,......,SAT=6
enum Weekday {SUM,MON,TUE,WED,THU,FRI,SAT};
- 枚举元素是常量,不能对他们赋值,如:SUM=0;
- 定义时,给枚举元素指定不同值:
enum Weekday{SUN=7,MON=1,TUE,WED,THU,FRI,SAT};
- 整数不能直接赋值给枚举变量,需要时,使用强制类型转换
#include
using namespace std;
//定义枚举类型
enum GameResult {WIN,LOSE,TIE,CANCEL};
int main() {
//定义该枚举类型变量,两种方式,可以带enum或者不带
GameResult result;
enum GameResult omit = CANCEL;
for(int count = WIN; count <= CANCEL; count++){
result = GameResult(count);
}
}
- 枚举值可以赋值给int,但是int类型不能直接赋值给枚举类型变量
auto类型
- auto:编译器通过自动推断变量类型
- 例如:auto val = val1 + val2;
decltype类型
- 定义一个变量与某一表达式类型相同
- 例如:decltype(i) j=2;
- 表示j以2作为初始值,类型与i一致
函数的参数传递
- 函数被调用时才分配形参的存储单元
- 实参类型必须与形参相符,否则编译器会进行隐含类型转换
- 值传递是传递参数值,即单向传递
- 引用可以实现双向传递
- 常引用用于保护实参安全(即使得实参不被改变)
- 引用开销小,且有时候不希望双向传递
- 引用类型:定义引用时必须初始化,使他指向一个已存在的对象
int i,j;
//定义int引用ri,并初始化为变量i的引用
int &ri = i;
含有可变个数参数的函数
- 如果所有实参类型相同,可以传递名为initializer_list的标准类型
- 如果实参类型不同,需要编写可变参数的模板
内联函数
- 调用简单函数时,能提高运行效率的机制,不需要经过转子函数返回这样的要花费开销的过程
- 通过编译器实现的,实际上是编译时使用函数体中语句,替换函数调用表达式
- 内联函数声明时使用关键字inline,这是对编译器的一个建议(不一定最终采纳)
#include
using namespace std;
const double PI=3.1415926;
inline double calArea(double radius){
return PI*radius*radius;
}
int main(){
double r = 3.0;
double area = calArea(r);
cout << area << endl;
return 0;
}
带默认参数值的函数
- 预先设置默认参数值,调用时给出实参则采用,否则采用预先设置的默认参数值
int add(int x=5, int y=6){
return x+y;
}
int main(){
add(10,20);//10+20
add(10);//10+6
add();//5+6
}
- 默认参数的形参必须在形参列表的最右端
- 调用时实参与形参结合顺序时从左向右
函数重载
- 由静态多态性实现,在编译阶段实现
- 函数重载:允许功能相近参数类型不同、参数个数不同、顺序不同的函数使用相同函数名
int sumofsquare(int a, int b){
return a*a+b*b;
}
double sumofsquare(double a,double b){
return a*a+b*b;
}
int main(){
int m,n;
cin >> m >> n;
cout << sumofsquare(m,n) << endl;//调用第一个,根据参数类型
double x,y;
cin >> x >> y;
cout << sumofsquare(x,y) << endl;
return 0;
}
类与对象
- 对象:现实中对象的模拟,具有属性和行为。
- 类:同一类对象的共同属性和行为。
- 定义对象时,通过构造函数初始化。
- 删除对象时,通过析构函数释放资源。
- 面向对象基本特点抽象、封装、继承、多态
类和对象的定义
- 类定义的语法形式
- 公有成员:任何外部函数都可以访问公有类型数据和函数
- 私有成员:只允许本类中的函数访问,而外部任何函数不能访问
- 保护成员:与私有成员很想,在继承和派生时不同
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护型成员
};
- 类内初始值
class Clock{
public:
void setTime(int newh, int newm, int news);
private:
//类内初始值
int hour = 0, minute = 0, second = 0;
};
- 对象定义语句
Clock myClock;
- 类的成员访问:
- 类中成员访问直接使用成员名
- 从类外访问成员使用“对象名.成员名”方式访问public成员
- 类的成员函数
- 在类中声明函数原型
- 可以在类外给出函数体实现,并在函数名前使用类名加以限定
- 也可以直接在类中给出函数体,形成内联成员函数
- 内联成员函数:1 将函数体放在类的沈明中,2 使用inline关键字
- 允许声明重载函数和带默认参数值的函数
- 类的定义和对象的使用
#include
using namespace std;
class Clock{
public:
void setTime(int newh=0, int newm=0, int news=0);
void showTime();
private:
int hour, minute, second;
}
void Clock::setTime(int newh=0, int newm=0, int news=0){
hour = newh;
minute = newm;
second = news;
}
void Clock::showTime(){
cout << hour << ":" << minute << ":" << second;
}
int main(){
Clock myClock;
myClock.setTime(8,30,30);
myClock.showTime();
return 0;
}
构造函数
- 类中用于描述初始化算法的函数,在对象被创建时使用特定值构造对象
- 构造函数的形式:
- 函数名与类名相同
- 不能定义返回值
- 可以有形参也可没有形参
- 可以是内联函数
- 可以重载
- 可以带默认值
- 构造函数在对象被创建时自动调用
- 如果程序中已经定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果依然希望编译器隐含生成默认构造函数,可以使用“类名() = default”,如:“Clock() = default”
class Clock{
public:
//构造函数
Clock() = default;
Clock(int newh, int newm, int news);
private:
int hour, minute, second;
};
//构造函数的实现
//下列是初始化列表方式初始化
Clock::Clock(int newh, int newm, int news):hour(newh),minute(newm),second(news){}
//自动调用构造函数
int main(){
Clock c1(8,10,0);//调用有参数的构造函数
Clock c2;//调用无参数的构造函数
return 0;
}
委托构造函数
- 委托另一个构造函数初始化成员
- 以下情况使用委托构造函数
Clock::Clock(int newh, int newm, int news):hour(mewh),minute(newm),second(news){}
Clock::Clock():hour(0),minute(0),second(0){}
//上述写法委托构造函数的版本
Clock::Clock(int newh, int newm, int news):hour(mewh),minute(newm),second(news){}
Clock::Clock(0,0,0){}//无参数的构造函数调用有参数的构造函数,将默认的初始化参数传给由参数表的构造函数
复制构造函数
- 复制构造函数规定:如何用已经存在的对象初始化新对象
- 不定义时,编译器生成默认的复制构造函数(实现两个对象的每个数据成员间的一一复制)
- 复制构造函数的定义:
- 复制构造函数时一种特殊的构造函数,其形参是本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象
class 类名{ public: 类名(形参); 类名(const 类名 &对象名);//复制构造函数,const是为了保护实参不被修改 //... }; 类名::类名(const 类名 &对象名){ 函数体 }
- 三种情况会调用复制构造函数:
- 定义一个对象时,以另一对象作为初始值
- 函数形参是类的对象,调用函数时,将使用实参对象初始化形参对象,此时发生复制构造
- 如果函数返回值是类的对象,函数执行完成返回主调函数是,将使用return语句中的对象初始化一个临时的无名对象,传递给主调函数,此时发生复制构造
class Clock{
public:
//构造函数
Clock() = default;
Clock(int newh, int newm, int news);
Clock(const Clock& p) = delete;//指示编译器不生成默认复制构造函数
private:
int hour, minute, second;
};
析构函数
- 析构函数:对象生存期消亡时,所需要的一些清理工作(生存期结束时,析构函数会自动调用)
- 如果不定义,编译器自动生成,此时函数体为空
- 析构函数:
~类名()
- 析构函数没有参数,没有返回类型
#include
using namespace std;
class Point{
public:
Point(int xx, int yy);
~Point();
//其他函数原型
private:
int x,y;
};
Point::Point(int xx, int yy){
x = xx;
y = yy;
}
Point::~Point(){
}
//其他函数实现
类的组合
- 组合的概念: 类中成员是另一个类的对象,可以在已有抽象的基础上实现更复杂的抽象
- 类组合的构造函数设计:
- 原则:不经负责对本类中的基本类型成员数据初始化,也要对对象成员初始化(不能直接访问对象的私有成员,通过调用对象的所属类的构造函数初始化对象成员)
类名::类名(对象成员所需的形参,本类成员形参):对象1(参数),对象2(参数),......{ //函数体的其他语句 }
- 构造组合类对象时的初始化次序
- 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序
- 初始化列表中未出现的成员对象,调用默认构造函数初始化
- 处理完初始化列表之后,再执行构造函数的函数体
- 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序
前向引用声明
- 类应先声明后使用
- 如果需要在某个类的声明之前,引用该类,则应该进行前向引用声明
- 前向引用声明只为程序引入标识符
class B;
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
- 注意事项
- 在提供完整的类声明之前,不能声明该类的对象,也不能在内敛成员中使用该类对象
- 即:当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节
class Fred; class Barney { Fred x;//要声明对象起码要知道字节数,而此时不知道细节,发生错误 }; class Fred { Barney y; };
结构体
- 结构体是一种特殊形态的类
- 与类的唯一区别
- 类的缺省访问权限是private
- 结构体的缺省访问权限是public
- 什么时候使用结构体而不用类
- 定义主要用来保存数据,而没有什么操作的类型
- 人们习惯将结构体的数据成员设置为公有,因此这时用结构体方便
- 结构体的的定义
struct 结构体名称 {
公有成员;
protected:
保护型成员;
private:
私有成员;
};
- 结构体中可以有数据成员和函数成员
- 结构体初始化
- 如果结构体的全部数据成员都是公共成员
- 没有用户定义的构造函数
- 没有基类和虚函数
- 那么可以用下面语法初始化
类型名 变量名 = {成员数据1初始值,成员数据2初始值,......};
#include
#include
#include
using namespace std;
struct Strudent {
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;
}
联合体
- 与类,结构体很像,但是联合体的目的是存储空间的共用
- 联合体的成员不是同时存在,同时起作用的,他们必须不同时存在
- 定义形式:
//下面这些成员不是同时存在,同时起作用的,他们必须不同时存在
union 联合体名称 {
公有成员
protected:
保护型成员
private:
私有成员
};
- 特点:
- 成员共用同一组内存单元,空间安装成员中最大分配
- 任何两个成员不同时有效
- 举例说明:
union Mark {
char grade;//等级制成绩
bool pass;//是否通过
int percent;//百分制
}
- 无名联合体
union {
int i;
float f;
};
//在程序中这样使用
i = 10;
f = 2.2;
枚举类
- 强类型枚举
- 枚举类定义
- 语法形式
enum class 枚举类型名:底层类型{枚举值列表}
- 语法形式
- 例:
enum class Type{General, Light, Medium, Heavy};//默认为int类型
enum class Type:char{General, Light, Medium, Heavy};//这些枚举常量是字符型的
enum class Gategory{General=1, Pistol, MachineGun, Cannon};
- 枚举类相对简单枚举类型优势
- 强作用域:枚举值作用域限制在枚举类中
//使用Type的枚举值General: Type::General
- 转换限制:枚举类对象不能和整明隐式地相互转换
- 可以指定底层类型
enum class Side{Right,Left};
enum class Ting{Wring,Right};
int main(){
Side s = Side::Right;
Ting w = Thing::Wrong;
cout << (s == w) << endl;
return 0;
}
- 实验一:构造函数,析构函数
#include
using namespace std;
enum CPU_Rank {P1=1,P2,P3,P4,P5,P6,P7
};
class CPU{
public:
CPU(CPU_Rank r, int f, float v){
rank = r;
fraquency = f;
voltage = v;
cout << "构造了一个CPU" << endl;
}
~CPU(){
cout << "析构了一个CPU" << endl;
}
void run(){cout << "CPU开始运行" << endl;}
void stop(){cout << "CPU停止运行" << endl;}
private:
CPU_Rank rank;
int fraquency;
float voltage;
}
int main(){
CPU a(P6,300,2.8);
a.run();
a.stop();
return 0;
}
- 实验二:类的组合
class COMPUTER{
private:
CPU my_cpu;
unsigned int storage_size;
unsigned int bandwidth;
public:
COMPUTER(CPU c, unsigned int s, unsigned int b){
my_cpu = c;
storage_size = s;
bandwidth = b;
cout << "构造函数" << endl;
}
~COMPUTER() {
cout << "析构函数" << endl;
}
void run(){
my_cpu.run();
cout << "computer 开始运行" << endl;
}
void stop(){
my_cpu.stop();
cout << "computer停止运行" << endl;
}
}
int main() {
CPU a(CPU_Rank::P6, 300, 2.8);
a.run();
a.stop();
cout << "*********" << endl;
COMPUTER my_computer(a, 100, 1000);
cout << "********" << endl;
my_computer.run();
my_computer.stop();
return 0;
}
数据的共享与保护
- 变量和对象的作用域、可见性、生存期
- 属于整个类的数据成员————静态数据成员
- 用于处理静态数据成员的函数————静态函数成员
- 友元:对一些类外的函数、其他类,给予预授权,使之可以访问私有成员
- 通过const关键字,限制对共享数据的修改
标识符的作用域和可见性
- 作用域分类:
- 函数原型作用域
- 局部作用域(块作用域)
- 类作用域
- 文件作用域
- 命名空间作用域
- 函数原型作用域(即声明时的参数作用域):
- 函数原型中的参数
- 作用域起始于函数形参表的左括号“(”,结束于右括号“)”
double area(double radius);
- 局部作用域
- 类作用域
- 类的成员具有类作用域
*其范围包括类体和成员函数体 - 在类作用域以外访问类的成员
- 静态成员:通过类名,或者该类对象名、对象引用访问
- 非静态成员:通过类名,类对象名、对象引用、对象指针访问
- 类的成员具有类作用域
- 文件作用域
- 不在前述各个作用域中出现的声明,就具有文件作用域
- 其作用域起始于声明点,结束于文件尾
- 可见性
- 即使在作用域内,也要是具有可见性,只有具有可见性,才能够访问
- 可见性时从对标识符的引用角度来谈的概念
- 可见性表示从内层作用域向外层作用域看时,能看见什么
- 如果标识符在某处可见,就可以在该处引用此标识符
对象的生存期、
- 含义:对象产生到结束期间就是他的生存期
- 静态生存期(静态不是指对象不可变):
- 这种生存期与程序运行期间相同
- 在文件作用域中声明的对象具有这种生存期
- 在函数内部声明静态生存期对象,要冠以关键字static
- 第二次进入不在初始化
- 动态生存期
- 开始于声明点,结束于命名该标识符的作用域结束处
- 块作用域中声明的,没有用static修饰的对象是动态生存期对象(习惯称作局部生存期对象)
- 第二次进入该区域会重新初始化
- 例子
类的静态数据成员
- 用关键字static声明
- 为该类的所有对象共享,静态数据成员具有静态生存期
- 声明在类体里面,但必须在类外定义和初始化,用(::)来指明所属的类
- 因为静态数据成员是属于类的,所以用“::”来指明所属的类
#include
using namespace std;
class Point{
public:
Point(int x=0,int y=0):x(x),y(y){
count++;
}
Point(Point &p){
x = p.x;
y = p.y;
count++;
}
~Point(){
count--;
}
int getx(){
return x;
}
int gety(){
return y;
}
void showcount(){
cout << "count = " << count << endl;
}
private:
int x,y;
static int count;
};
int Point::count = 0;
int main(){
Point a(4,5);
cout << "Point a: " << a.getx() << "," << a.gety() << endl;
a.showcount();
Point b(a);
cout << "Point b: " << b.getx() << "," << b.gety() << endl;
b.showcount();
return 0;
}
- 关键字static的作用:使对象或对象具有全局的寿命,当程序第二次访问同一个块时,不需要重新初始化,直接引用当前值
- static上述特性,刚好用于定义静态数据成员,使得该类下的所有对象共享同一个值。
类的友元
- 类的友元使得属于该类友元的类或函数可以访问该类的私有成员
- 友元使C++提供的一种破坏数据封装和数据隐藏的机制
- 通过将一个模块声明为另一个模块的友元,一个模块可以引用另一个模块本被隐藏的信息
- 可以声明友元函数,和友元类
- 友元函数
- 友元函数使在类声明中由关键字friend修饰说明的非成员函数,在他的函数体中能通过对象名访问private和protected成员
- 作用:增加灵活性
- 访问对象中的成员必须通过对象名
#include
#include
using namespace std;
class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {}
int getx() { return x; }
int gety() { return y; }
friend double dist(const Point& a, const Point& b);
private:
int x, y;
};
double dist(const Point& a, const Point& b) {
double x = a.x - b.x;
double y = a.y - b.y;
return (sqrt(x * x + y * y));
}
int main() {
Point p1(1, 1), p2(2,2);
cout << "the distance is: " << dist(p1, p2) << endl;
return 0;
}
- 友元类
- 若一个类为另一个类的友元,则此类的所有成员都可以访问对方类的私有成员
- 声明语法:将友元类名在另一个类中使用friend修饰说明
class A{
friend class B;
Public:
void display(){
cout << x << endl;
}
private:
int x;
};
//B的成员是另一个类对象,所以是组合类
class B{
public:
void set(int i);
void display();
private:
A a;
};
void B::set(int i){
a.x=i;//连A类对象自己都不能直接访问Private成员x;
}
void B::display(){
a.display();
}
int main(){
A a;
B b;
b.set(11);
b.display();
return 0;
}
- 类的友元关系是单向的
共享数据的保护
- 如果既要共享数据,又想保证数据安全性(不被修改),就需要将数据定义为常类型,即用const修饰
- 常类型
- 常对象:必须初始化,不能被更新
const 类名 对象名;
- 常成员:用const修饰的类成员,有常数据成员和常函数成员
- 常引用:被引用的对象不能被更新,即只读的引用
const 类型说明符 &引用名
- 常数组
- 常指针
- 常对象:必须初始化,不能被更新
多文件结构和编译预处理命令
- 外部变量
- 除了在定义它的源文件中可以使用外,还能被其他文件使用
- 文件作用域(全局作用域)下定义的变量,默认为外部变量
- 在其他文件中如果需要使用,需要用extern关键字声明
- 外部函数
- 在所有类之外声明的函数,都是具有文件作用域的
- 这样的函数可以在不同编译单元被调用
- 只要在调用之前进行引用性声即可(即声明函数原型)
- 只有全局变量才能被其他文件中的extern引用到:
- 这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说main.c要引用到value,不只是取决于在main.c中声明extern int value,还取决于变量value本身是能够被引用到的。这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量。
- extern声明的变量的作用域取决于声明位置:
- 还有很重要的一点是,extern int value可以放在main.c中的任何地方,比如你可以在main.c中的函数fun定义的开头处声明extern int value,然后就可以引用到变量value了,只不过这样只能在函数fun作用域中引用value罢了,这还是变量作用域的问题。
- 有时并不希望当前文件中定义的标志符被拿到其他文件中使用
数组
数组的定义与使用
- 数组是具有顺序关系的若干相同类型变量的集合体,组成数组的变量称为该数组的元素
- 数组的定义
类型说明符 数组名[常量表达式][常量表达式]......;
- 数组元素的使用
- 数组需要先定义后使用
- 可以逐个引用数组
数组的存储和初始化
- 一维数组的存储
- 数组元素在内存中顺序存放,他们的地址是连续的
- 数组名字是数组的首元素的内存地址
- 数组名是一个常量,不能被赋值
- 一维数组的初始化
- 列出全部元素的初始值
static int a[10]={0,1,2,3,4,5,6,7,8,9};
- 给一部分元素指定初值
static int a[10]={0,1,2,3,4};
- 列出全部元素时,可以不指定数组长度
static int a[]={0,1,2,3,4,5,6,7,8,9};
- 列出全部元素的初始值
- 二维数组的存储
- 是一维数组的数组
- 二维数组的初始化
- 将所有处置写在一个{}内,按顺序初始化
static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
- 分别列出二维数组元素处置
static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
- 只对部分元素初始化
static int a[3][4]={{1},{5,6,},{9,10,11}};
- i而出全部初始值时,一维下标个数可以省略
static int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
- 将所有处置写在一个{}内,按顺序初始化
- 初始化问题
- 如果不做任何初始化,局部作用域的非静态数组中会存在垃圾数据,static数组中数据会默认初始化为0
- 如果部分初始化,剩余部分默认初始化为零
对象数组
- 对象数组的定义与访问
- 定义对象数组:
类名 数组名[元素个数]
- 访问对象数组元素:
数组名[下标].成员名
- 定义对象数组:
- 对象数组的初始化
- 数组中每个元素被创建时,系统会调用类构造函数初始化对象
- 通过初始化列表赋值:
Point a[2] = {Point(1,2),Point(3,4)};
- 如果没有为数组指定显式的初始值,数组元素使用默认值初始化
基于范围的FOR循环
- 处理容器类的基于范围的FOR循环:能够自动依次处理容器中的全部数据
#include
int main(){
int array[3] = {1,2,3};
for(int &e : array){
e += 2;
std::cout << e << std::endl;
}
return 0;
}
指针
指针的概念、定义和指针运算
- 内存空间的访问方式
- 通过变量名访问
- 通过地址访问
- 指针的概念
- 指针:内存地址,用于访问内存单元
- 指针变量:用于存放地的址变量
- 指针变量的定义和访问
static int i;
static int* ptr = &i;//“&”取址运算符
*ptr = 3;//通过指针访问变量
指针的初始化和赋值
- 指向常量的指针
- 指针常量
- 指针常量:指针本身是常量,指针的值不能改变
int a,b; int * const p2 = &a; p2 = &b;//错误,p2是指针常量,不能改变