基本程序设计
1.sizeof
sizeof(<datatype>)
返回值为参数数据类型所占字节数的大小。
2.C++类型转换
- 格式:类型转换操作符<类型说明符>(表达式)
- 类型转换操作符:const_cast,dynamic_cast,reinterpret_cast,static_cast
- 常用static_cast,如static_cast(z)
- C语言中的类型转换方式也可使用
3.数据输入输出流
- C++中,数据从一个对象到另一个对象叫做“流”。从流中获取数据称为提取操作,向流中输出数据称为插入操作
- C++数据输入输出不需要函数,利用I/O流来实现,cin和cout是预定义的流类对象
- cin处理标准输入,即键盘输入,cout用来处理标准输出,即屏幕输出
- "<<“和”>>"是预定义的插入符和提取符,作用在流类对象cout和cin上便可实现对应流操作
- 系统标准输入输出可以通过系统设置来改变,也可以使用函数调用来改变
注:c++文件有输入输出操作时要包含头文件iostream.h
操纵符名 | 含义 |
---|---|
dec | 十进制表示 |
hex | 十六进制表示 |
oct | 八进制表示 |
ws | 提取空白符 |
endl | 插入换行符,并刷新流 |
ends | 插入空字符 |
setprecision(int) | 设置浮点数的小数位数(含小数点) |
setw(int) | 设置域宽 |
eg. cout<<setw(8)<<setprecision(5)<<z<<endl
4.类型关键字auto和decltype
auto:编译器通过初始值自动判断变量类型
- 如 auto val = val1 + val2.
decltype:定义一个变量与某一表达式类型相同,但不使用它的初值
- 如 decltype(i) j = 2. j的类型与i一致,初值是2
函数
1.内联函数
- 作用:调用函数时提高效率
- 内联的含义:在程序编译时,利用函数体语句按一定规则替代函数调用语句。这是由编译器帮我们实现的,结果是看起来是一个函数,但实际编译执行并非按照函数的调用、返回步骤执行,节约程序运行时间
- 声明:inline
- 要求:内联函数不能用循环语句和switch语句;内联函数的定义必须在其被第一次调用之前;不能进行异常接口声明
- 注意:inline只是对编译器的建议,并非一定按照内联函数处理代码
2.constexpr
- 这是一个常量表达式函数,用来初始化常量,有c++11标准提供
- constexpr修饰的函数,在其所有参数均为constexpr时一定返回constexpr
eg. constexpr int m{return 20;}
constexpr int c = m();
3.带默认参数值的函数
- 可以预先设置默认的参数值,调用时如给出实参,则采用实参,否则采用默认参数值
- 有默认参数的形参必须列在形参列表的最右,即默认参数值的右边不能用无默认值的形参
- 调用时实参与形参的结合次序是从左向右
4.函数重载
- 功能相近的函数在相同作用域多次定义
- 重载函数的形参必须不同:个数不同或类型不同
- 编译程序将根据实参和形参的类型与个数的最佳匹配决定调用哪一个函数
5.C++系统函数
- 使用系统函数时要包含头文件cmath
- sin,sqrt,cos等均在其中
类和对象
1.面向对象程序的特点
- 抽象:对同一类对象的共同属性(数据)和行为(函数)进行概括
- 封装:将抽象出来的数据和代码封装在一起,形成一个类
- 继承:在已有类的基础上进行扩展形成新的类
- 多态:同一名称,不同的功能实现方式。这样可以达到行为标识的统一,减少程序中标识符的数目
2.类和对象的定义
对象是类的一个实例,类是对象的一种设计。
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护成员
}
类名称 对象名
- 数据成员可以设置类内初始值
class Clock{
public:
int showTime();
int setTime(int h, int m, int s);
private:
int h = 0;
int m = 0;
int s = 0;
}
Clock C1;
-
公有成员:任何外部函数都可以访问
-
私有成员:只允许本类中的函数访问
– 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略 -
保护成员:在继承和派生时对派生类的影响不同
-
类中成员直接使用成员名相互访问,类外访问公有成员使用 对象名.成员名 进行访问
-
类的成员函数
– 可以在类中和类外给出函数体的实现
– 类外实现时函数名前使用类名加以限定
– 允许函数重载和带默认值
– 内联成员函数也可以被定义,定义要放在类的声明中并用inline声明
#include<iostream.h>
using namespace std;
int Clock::showTime()
{
cout<<h<<:<<m<<:<<s<<endl;
}
int main()
{
Clock C1;
C1.showTime();
return 0;
}
3.构造函数
- 类中的特殊函数,用于描述初始化算法
- 构造函数的形式:
– 函数名与类名一致
– 不能定义返回值类型,不能用return
– 可以有形式参数,也可以没有形式参数
– 可以是内联函数
– 可以重载
– 可以带默认值 - 构造函数在对象创建时被自动调用
- 默认构造函数可以不需要实参,以下这两种不能同时出现
– 带默认值的构造函数
– 不带实参的构造函数 - 隐含生成的构造函数
– 如果程序中未定义构造函数,编译器将自动生成一个默认构造函数,参数表为空
– 如果类内定义了成员的初始值,则使用类内定义的初始值
– 如果类内没有定义成员的初始值,则以默认方式初始化,这样初始化的值是不确定的
class Clock{
public:
Clock(int h,int m,int s);
int showTime();
int setTime(int h, int m, int s);
private:
int hour, min, sec;
}
//构造函数的定义,简单初始化的推荐方式
Clock::Clock(int h, int m,int s):
hour(h),min(m),sec(s){
}
int main()
{
Clock C1(12,0,0);//自动调用构造函数
C1.showTime();
return 0;
}
class Clock{
public:
Clock(int h,int m,int s);//构造函数
Clock();//默认构造函数
int showTime();
int setTime(int h, int m, int s);
private:
int hour, min, sec;
}
Clock::Clock():hour(0),min(0),sec(0){
}
int main()
{
Clock C1(12,0,0);
Clock c2;
}
- 委托构造函数
– 一个构造函数委托另一个构造函数来进行初始化
– 保持构造函数的一致性
Clock::Clock(int h, int m, int s):
hour(h), min(m) ,sec(s){}
Clock::Clock:Clock(0,0,0){}
- 复制构造函数
– 用一个已经存在的对象去初始化同类型的新变量
– 当我们不对复制构造函数进行定义时,编译器会自动在上述情况发生时生成复制构造函数,做到成员一一对应
– 当我们不想使两个对象一一对应时,就要自己定义复制构造函数
– 复制构造函数的形参为本类的对象的引用,作用是利用一个已经存在的对象去初始化同类型的新对象
– 在函数形参的参数传递和返回时都会调用复制构造函数,用来构造临时无名对象做参数传递
class 类名{
public:
类名 (形参);//构造函数
类名(const 类名 &对象名);//复制构造函数
}
类名::类名(const 类名 &对象名)
{函数体}
Clock() = default;//指示编译器使用默认构造函数
Clock(const Clock &C) = delete;//指示编译器不生成复制构造函数
4.析构函数
- 对象消亡时的善后机制
- 对象被删除前的一些清理工作
- 未定义析构函数时,编译器会自动生成函数体为空的析构函数
- 参数表必须为空,不能有返回值
Clock::~Clock(){}
5.类的组合
- 类中的成员是另一个类的对象
- 组合类的构造函数设计
– 不仅要负责本类中基本类型成员的数据的初始化,还要对对象成员初始化
– 组合类对象的构造次序:首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序;处理完初始化列表之后,再执行构造函数的函数体
– 对象成员构造函数调用顺序:按对象成员的调用顺序,先声明者先构造
– 初始化列表中未出现的成员对象,使用默认构造函数进行初始化
类名::类名(对象成员所需的形参,本类成员形参):
对象1(参数),对象2(参数),……
{
//函数体其他语句
}
class Point{
public:
Point(int x1, int y1):x(x1),y(y1){}
private:
int x = 0;
int y = 0;
}
class Line{
public:
Line(Point p1, Point p2):a(p1),b(p2){}
private:
Point a;
Point b;
}
6.前向引用声明
- 在某个类使用之前声明使用
- 前向引用声明只为程序引入一个标识符,具体声明在其他地方
- 注意事项:
– 在提供一个完整的类声明之前,不能声明该类对象,也不能在内联成员函数中使用该类对象
– 当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节
class A;
class B
{
public:
void f(A a);
}
class A
{
public:
void f(B b);
}
7.UML
- 一种可视化语言
- 三种基本元素:事物、关系、图
- 对象图、类图
- 依赖关系、关联关系、包含关系、继承关系
8.结构体
- C++中的结构体是一种特殊的类
- 当不定义成员的类型是默认为公有成员
- C++的结构体可以有数据成员也可以有函数成员
- 与C中的结构体兼容
9.联合体
- 联合体的各个数据成员共用相同内存单元
union Mark
{
char grade;
bool pass;
int percent;
}
//无名联合体
union
{
int i;
float k;
}
10.枚举类
- 优势
– 强作用域:作用域在枚举类,使用必须用类名
– 转换限制
– 可以指定底层类型
//enum class Type:char{a,b,c};
//enum class type{a=1,b,c};
enum class Type{a,b,c};
enum class type{a,b,c};
//usage
Type s = Type::a;
type w = type::a;
s == w;//错误,不能直接比较
数据的共享与保护
1.类的静态数据成员
- 用关键词static声明
- 由该类的所有对象共享,具有静态生存期
- 必须在类外进行定义和初始化,用::指明所属类别
2.类的静态函数成员
- 主要用来处理静态数据成员
- 该函数不能知道调用自己的对象具体是哪一个
class Point
{
public:
static int count;
static void showCount();
}
Point::showCount()
{
cout<<count<<endl;
}
Point::count = 0;
Point::showCount();
3.类的友元
- 一种破坏数据封装和数据隐藏的机制
- 将一个模块声明为另一个模块的友元(friend),使得其可以访问它的隐藏信息
- 谨慎使用
- 分类:友元函数,友元类
– 友元函数:在类声明中由关键字friend修饰的非成员函数
– 友元类:这个类的所有成员都能访问另一类的私有成员 - 友元关系是单向的,一个类或函数被另一个类声明为友元时,该类或函数可以访问这个类的私有数据成员
class Point
{
public:
friend float ab(const Point &a, const Point &b);
private:
int x;
int y;
}
float ab(const Point &a, const Point &b)
{
return a.x - b.x;
}
注:类的传递通常使用指针传递,提高效率
class A
{
public:
friend class B;
private:
int x;
int y;
}
class B
{
public:
int set(int i);
private:
A a;
}
B::set{int i}
{
a.x = i;
}
4.共享数据的保护(const)
- 常对象
- 常成员
– 常数据
– 常成员函数 - 常引用
– 既能保证安全性,又能保证高效执行,避免参数在参数传递时重复初始化。相当于“只读”
– 常见的应用:复制构造函数
- 常成员函数
– 不更新对象的数据成员
–类型说明符 函数名(参数表) const
– const关键字可以被用于参与对重载函数的区分
– 通过常对象只能调用它的常成员函数
class A
{
public:
void print();
void print() const;
private:
int a = 0;
}
void A::print()
{
a = a + 1;
cout<<a<<endl;
}
void A::print() const
{
cout<<a<<endl;
}
void main()
{
A a1;
const A a2;
a1.print();
a2.print();
}
class A
{
public:
A(int i);
private:
const int a;
static const int b;
}
const int A::b = 10;//类外对静态常变量做初始化
A::A(int i): a(i){}//构造函数,注意:只能在函数体外对常变量进行初始化
数组和指针
1.基于范围的for循环
- 自动遍历整个容器
int arr[3] = {1,2,3};
for(auto &e : arr)
{
e = e + 1;
cout<<e<<endl;
}
2.动态内存分配与释放
- 动态申请内存操作符 new
– new 类型名(初始化参数列表) - 释放内存操作符 delete
– delete 指针
– 指针必须是new操作返回的地址
3.申请和释放动态数组
- 分配:new 类型名T[数组长度]
– 数组长度可以是任何整型表达式,在运行时计算 - 释放:delete[] 指针
– 必须有[]
– 指针必须是new分配的数组首地址指针 - 动态创建多维数组:new 类型名T[第1维长度][第2维长度][第3维长度]……
– 这个指针是指向行的指针,当指针+1时指向的位置跳跃一整维
Point *p1 = new Point(1,2);
int *p2 = new int[5];
int (*p3)[8][5] = new int[3][8][5];
delete p1;
delete[] p2;
delete[] p3;
4.智能指针
- C++11标准提供的自动解决内存遗留问题的指针
- unique_ptr
– 不允许多个指针共享资源,可以用标准库中的move函数转移指针 - shared_ptr
– 多个指针共享资源 - weak_ptr
– 可以复制share_ptr,但其构造和释放对资源不产生影响
5.vector对象
- C++标准库中的一个类模板
- 更加方便、安全的数组类定义
– 封装任何类型的动态数组,自动创建和删除
– 数组下标越界检查 - 格式:vector<元素类型> 数组对象名(数组长度)
- 引用: vector对象名[下标表达式]
– vector数组对象名不表示数组首地址 - 获得数组长度:vector对象名.size()
//参数为vector引用
double average(const vector<double> &arr)
{
double sum = 0;
for(int i = 0; i < arr.size; i++)
{
sum = sum + arr[i];
cin>>arr[i]//这里是vector对[]进行了运算符重载
}
return sum;
}
vector<int> arr(3) = {1,2,3};
for(auto e : arr)
{
cout<<e<<endl;
}
for(auto e = arr.begin(); e != arr.end(); e++)
{
cout<<e<<endl;
}
6.对象复制与移动
- 浅层复制:实现对象间数据元素的一一对应复制。
– 指针复制的结果是将指针空间所存地址复制到新的对象中去,两个对象中的指针成员指向同一块内存空间,这会导致析构出错。 - 深层复制:当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指对象进行复制
ArrayPoint::ArrayPoint(const ArrayPoint &v)
{
size = v.size;
point = new Point[size];
for(int i = 0; i < size; i++)
point[i] = v.point[i];
}
7.移动构造
- C++11标准中提供的新的构造方法,只需要对对象进行转移而不是复制
- 源对象资源控制权全部交给目标对象
//这里 && 表示右值引用,即将消亡的值是右值
ArrayPoint::ArrayPoint(const ArratPoint &&v):points(v.points)
{
//C++中的空指针用nullptr表示
v.points = nullptr;
cout<<"Move constructor"<<endl;
}
8.string类
- 有效弥补C语言风格字符串处理操作缺陷,推荐在C++使用string类进行字符串处理
- 封装起来的字符数组
- 常用构造函数
– string()
– string(const char *s)
– string(const string &s) - 常用操作
– s + t 字符串的拼接
– s = t
– s == t
– s != t
– s < t
string s1 = "abc";
string s2 = "def";
string s3 = s1 + s2;
bool s = s1 < s2;
//string类的长度可以自动扩展
string s;
cin>>"string is:">>s;
cout<<"length is:"<<s.length()<<endl;
- getline 输入整行字符串(包含string.h文件)
– getline(输入方式,目标存储位置,[输入结束标志])
– getline(cin, s) getline(cin,s,’+’)
附录:
<cassert> (assert.h)
这是一个C语言的诊断库,assert.h文件中定义了一个可作为标准调试工具的宏函数: assert .下面介绍这个宏函数:assert
- 函数原型:void assert (int expression);
- 函数描述:
– 如果这个宏函数形式的参数(expression)等于零,也就是说参数的表达式等价于false,那么就会有一条消息会被写入到标准错误设备,并且调用abort()函数来终止程序的运行;
这条特殊消息的显示依赖于特定库的实现,但是至少包括: 错误参数的陈述,源文件的名称,发生错误的行号; - 函数参数: int expression
– expression可以是一个待计算的表达式,如果表达式的运算结果是0,这将导致断言失败(assertion failure)并终止程序; - 函数返回值: void
<cstdlib> cstdlib.h
- 函数原型:exit(int n)
- 功能:立即终止当前程序的执行,并且将一个整数返回系统,该整数的作用与由主函数返回的整数相同,0表示程序正常退出,1表示程序非正常退出