二 C++简单程序设计
- 全面兼容C:它保持了C的简洁、高效和接近汇编语言等特点;对C的类型系统进行了改革和扩充
- C++也支持面向过程的程序设计,不是一个纯正的面向对象的语言
| 类型 | 使用范围 |
|---|---|
| auto | 暂时存储在内存中 |
| register | 存放在通用寄存器中 |
| extern | 在所有函数和程序段中可引用 |
| static | 内存中固定地址存放,在整个程序运行期间有效 |
符号常量
例:
const float pi=3.1415926;
或 float const pi=3.1415926;
数据的输入和输出
>> 流提取符,<< 流插入符 预定义流类对象cin, cout
typedef语句
typedef 已有类型名 新类型名表;
联合体
与结构体的形式很像
三 函数
- 用函数计算两个数的最大公约数
1. 暴力轮询法
void MaxFactor(int n1, int n2)
{
int gcd = 1;
for(int i = 1; i < n1 && i < n2; ++i)
{
if(n1 % i == 0 && n2 % i == 0)
gcd = i;
}
return gcd;
}
2. 两数相减, 未曾设想的道路
void MaxFactor(int n1, int n2)
{
while(n1 != n2){
if(n1 > n2)
n1 -= n2;
else
n2 -= n1;
}
return n1;
}
3. 递归,更是未曾设想
void MaxFactor(int n1, int n2)
{
if(n2 != 0)
return MaxFactor(n2, n1%n2);
else
return n1;
}
- 用函数计算两个数的最小公倍数
1. 暴力轮询法
void MinMultiple(int n1,int n2)
{
int gmd = (n1 > n2 ? n1 : n2);
while(1)
{
if(gmd % n1 == 0 && gmd % n2 == 0)
break;
gmd++;
}
return gmd;
}
2. 利用最大公约数来求
最大公约数gcd,求法看上面
return ((n1*n2)/gcd)
内联函数
- 声明时使用关键字 inline。
- 编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销。
- 注意:
内联函数体内不能有循环语句和switch语句。
内联函数的定义必须出现在内联函数第一次被调用之前。
对内联函数不能进行异常接口声明。
带默认形参的函数
- 调用出现在函数体实现之前时,默认形参值必须在函数原形中给出。
- 调用出现在函数体实现之后时,默认形参值需在函数实现时给出。
如:
int add(int a = 5, int b = 6)
int main(){
int a,b;
while(cin >> a >> b)
return add();
}
int add(int a,int b)
{
return a+b;
}
又如
int add(int a = 5,int b = 6)
{
return a+b;
}
int main(){
int a,b;
while(cin >> a >> b)
return add();
}
- 默认形参值必须从右向左顺序声明,并且在默认形参值的右面不能有非默认形参值的参数。因为调用时实参取代形参是从左向右的顺序。
int add(int x,int y=5,int z=6); //正确
int add(int x=1,int y=5,int z); //错误
int add(int x=1,int y,int z=6); //错误
重载函数
- 必须是形参类型或者形参个数不同的同名函数
- 编译器不以返回值来区分,不能只有返回值不同
- 存在于父类、子类与同类之中
重写
- 返回值、参数、方法名完全相同
- 子类方法不能缩小父类方法的访问权限
- 子类方法不能抛出比父类方法更多的异常
- 存在于父类与子类之间
- 方法被定义为final不能被重写
C++系统函数
头文件cmath
四 类与对象
类的声明
前向引用声明注意事项
- 使用前向引用声明虽然可以解决一些问题,但它并不是万能的。
- 在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。
- 当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。
class Fred; //前向引用声明
class Barney {
Fred x; //错误:类Fred的声明尚不完善
};
class Fred {
Barney y;
};
编译的时候编译器报错
因为:Fred类没有定义
五 C++程序结构
作用域与可见性
- 函数原型作用域:void Circlelen(int radius)里面的参数只在函数内部才有效
- 块作用域:最熟悉的
- 类作用域:通过类的访问来作用
- 文件作用域:全局变量、静态变量
可见性:取小
生存周期
- 静态生存周期:文件作用域、静态变量。随着程序的生命周期而生存
- 动态生存周期:其他。开始于声明处,结束于作用域结束处
数据段共享和保护
###共享 静态变量:由类的所有对象共同保护
static 类型说明符 标识符
静态变量只能访问静态函数,访问非静态成员函数
保护
const常量
常量
格式: const 类型说明符 标识符
eg. const int number;
int static const number; // error!! static和const不能同时使用
需要初始化,定义后不可以改变其值
常函数
不能将const 放在函数名前面,不然返回的是类型为const的函数
格式: 类型说明符 函数名 ([参数]) const{}
eg. void getNumber(int a , int b) const{}
错误:
//number已经有初值
void getNumber(int a , int b) const{number = 34;}
正确:
void getNumber(int a , int b) const{return 34;}
不能进行改变初始量的操作
又如: Date 类中的三个常量成员函数:GetYear()、GetMonth() 和 GetDay()
class Date {
public:
int GetYear() const { return y; }
int GetMonth() const { return m; }
int GetDay() const { return d; }
void AddYear(int n); // add n years
private:
int y, m, d;
};
当在 GetYear() 中,修改成员变量 y 时,编译会报错
// error : attempt to change member value in const function
int Date::GetYear() const
{
return ++y;
}
注意 1):常量成员函数,在类外面实现时,const 后缀不可省略
// error : const missing in member function type
int Date::GetYear()
{
return y;
}
注意 2):成员变量前有 mutable,则在常量成员函数中,也可修改成员变量
class Date {
// ...
private:
mutable int y;
};
// ok, even changing member value in const function
int Date::GetYear()
{
return ++y;
}
六 指针、数组、字符串
一、 概念
- 数组:数组是用于储存多个相同类型数据的集合。
- 指针:指针相当于一个变量,但是它和不同变量不一样,他存放的是它变量在内存中的地址。
二、 赋值、存储方式、求sizeof、初始化等
- 同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或者拷贝
- 存储方式
- 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。
数组的存储空间不是在静态区,就是在栈上
- 指针:指向存储内容的地址。
由于指针本身就是变量,再加上它所存放的也是变量,所以指针的存储空间不能确定
- sizeof
- 数组:sizeof(数组名)/sizeof(数据类型)
- 指针:32位的sizeof(指针名)都是4byte,64位的sizeof(指针名)都是8byte
this 指针
const 成员函数,可被 const 或 non-const 型类对象调用,如 GetYear(),而 non-const 成员函数,只能被 non-const 对象调用,如 AddYear()
void func(Date& d, const Date& cd)
{
int i = d.GetYear(); // OK
d.AddYear(1); // OK
int j = cd.GetYear(); // OK
cd.AddYear(1); // error
}
任何成员函数,都有一个隐含的形参,即指向该类对象的 this 指针,它通常由编译器隐式的生成,通过 this 指针,类对象才能调用成员函数
一般的,this 指针默认指向 non-const 对象,因此,const 类对象,无法通过 this 指针调用 non-const 函数
// 默认的 this 指针,指向 non-const 对象
Date * const this;
而 const 成员函数,通过参数后的 const 后缀,修改其 this 指针指向 const 型对象,此时,const 对象便可调用 const 成员函数
// 常量成员函数中的 this 指针,指向 const 对象
const Date * const this;
const 接口
上文的 func() 函数中,形参用 const 来修饰,可保证数据传递给函数,而本身不被修改,是设计 "接口" 的一种常用形式
void func(Date& d, const Date& cd)
{
// ... ...
}
特殊情况下,想要 const 对象调用 non-const 成员函数,则可用 const_cast 移除对象的 const 属性,具体形式为: const_cast(expression)
void func(Date& d, const Date& cd)
{
int j = cd.GetYear(); // OK
const_cast<Date&>(cd).AddYear(1); // const 对象调用 non-const 成员函数
}
这种做法,破坏了用 const 来指定 "接口" 的本意,并不推荐
友元
class A{
public A(int a){};
private:
int n,m;
friend void func1();
friend class B
};
void func1(){};
class B{};
友元破坏了类的封装性和数据的私密性
六 数组和指针
- 常量指针:也被称为指向常量的指针,const T* ptr不能通过指针来改变所指对象的值,但指针本身可以改变,可以指向另外的对象。
int x=5,y=10;
const int * iptr=&x; //指向常量的指针
*iptr=7; // 错误,不能通过指针修改所指对象的值
x=7; //正确,x的值可以改变
iptr= &y; //正确,ipt的本身的值可以改变
- 指针常量:也称为指针类型的常量,T* const ptr 指向的地址不能修改,指向内容可以修改
int x = 5,y = 10;
int *const iptr=&x;
*iptr = 7; // OK!
iptr = &y; // wrong!
- 指向常量的指针常量: const T* const ptr
数组名F是常量,是数组F的首地址
指针
普通变量不能用void类型,但是指针可以用void类型
void number; //error
void * numb; // ok
给指针赋值
格式一:先声明,再赋值
类型说明符 *标识符;
标识符 = &值;
格式二:直接定义
类型说明符 *标识符 = &值;
int a,b;
int *pa,*pb=&b;
pa=&a;
声明与赋值
例:int a[10], *pa;
pa=&a[0]; 或 pa=a;
通过指针引用数组元素 经过上述声明及赋值后:
*pa就是a[0],*(pa+1)就是a[1],... ,*(pa+i)就是a[i].
a[i], *(pa+i), *(a+i), pa[i]都是等效的。
不能写 a++,因为a是数组首地址是常量。
指针型函数和指向函数的指针
指针函数返回指针,而函数指针存储函数的地址。
类型说明符 *函数名 (参数列表);//指针型函数
int * getArray(int n){
static int *a = new int [n];
for(int i=0;i<n;i++)
a[i]=2*i;
return a;
}
int main()
{
int num = 5;
int* b = getArray(num);
for(int i=0;i<num;i++)
cout<<b[i]<<endl;
}
相当于把*a赋值于*b
类型说明符 (*函数指针名)(参数列表);//函数指针
函数指针名 = 函数名
void (*funPtr)(float);
void printFloat(float F){};
funPtr = printFloat;
funPtr(pi)
加上类的运用之后,会出来一系列的比如:通过对象指针访问成员数据,通过对象指针访问成员函数,通过对象名访问成员,通过类指针访问成员
int (*Point::Pointptr)() = &Point::func1;
Point A(1,2);
Point *ptr = &A;
cout << (A.*Pointptr)() << endl;
cout << (ptr->func1)(); /对象指针名->函数名
指向类的非静态成员指针
- 指向对象成员的指针也要先声明,再赋值,然后引用。
声明指向对象成员的指针
声明指向公有数据成员的指针
类型说明符 类名::*指针名;
- 声明指向公有函数成员的指针
类型说明符 (类名::*指针名)(参数表);
对数据成员指针的赋值:
指针名=&类名::数据成员名
对数据成员指针的引用:通过对象名(或对象指针)与成员指针结合来访问数据成员
对象名.* 类成员指针名
或: 对象指针名->*类成员指针名
对成员函数指针的赋值:
指针名=&类名::成员函数名;
对成员函数指针的引用:必须通过对象调用非静态成员函数
(对象名.* 类成员指针名)(参数表)
或: (对象指针名->类成员指针名)(参数表)
#include<string>和#include<cstring>
- string头文件可以进行+ - += -=操作
- cstring包含一些C语言中对于字符串的操作:strcmp, strlen,strcpy等
七 继承与派生
派生的几种方式
- 如果不显式给出,系统默认是private
- 派生类继承的过程
- 吸收基类成员:吸收除构造函数和析构函数的所有成员函数和数据
- 改造基类成员:访问控制、同名隐藏
- 添加新的成员:派生类的构造函数和析构函数
- 公有继承:public保持public,protected保持protected,private无法访问
class B: public class A,派生类成员能够访问前两者,类外部只能访问public
- 私有继承:所有成员保持private,父类中的private无法继承
class B: private class A派生类内部成员可以访问来自父类的public和protected,类的外部无法访问任何成员
- 保护继承:所有成员保持protected, private属性成员无法继承
class B: protected class A派生类成员能够访问public和protected,类外部不能访问任何成员
构造函数和析构函数调用顺序
- 如果基类声明了带有形参表的构造函数时,派生类就需要声明自己的构造函数,保证基类进行初始化时能够获得必要的数据。
- 构造派生类的对象时,要对基类数据成员、新增数据成员和成员对象的数据进行初时化。
- 在构造派生类的对象时, ①调用基类的构造函数 ② 隐含调用内嵌对象成员的构造函数 ③ 执行派生类构造函数的函数体。
派生类名::派生类名(参数总表):基类名1(参数表1), 基类名2(参数表2), ...基类名n(参数),内嵌对象名1(内嵌对象参数表1),…内嵌对象名m(内嵌对象参数表m
- 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
- 调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。
- 派生类的构造函数体中的内容。
CBAFDE
复制构造函数
若建立派生类对象时调用默认拷贝构造函数,则编译器将自动调用基类的默认拷贝构造函数。
若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。例如:
C::C(C &c1):B(c1)
{…}
类型兼容
- 派生类对象继承基类
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来代替:
派生类的对象可以隐含转换为基类对象。
派生类的对象可以初始化基类对象的引用。
派生类对象的地址可以赋给指向基类的指针。
在替代之后,派生类对象可以作为基类的对象使用,但只能使用从基类继承的成员。
抽象类
- 定义:类当中,如果有virtual的成员数据或者成员函数,那么这个类是虚类,虚类不能实例化,需要被继承后的非虚类来覆盖函数
- 虚函数是动态绑定的函数
- 构造函数不能是虚构函数,析构函数可以是虚构函数
- 何时需要虚析构函数?通过基类指针删除派生类对象时
const和virtual可以同时使用
virtual double area() const {return c*c*3.14;}
纯虚函数
写法:
virtual int func1()=0;一定要被重写
虚基类
- 防止产生歧义,在继承的过程中加上一个virtual
- 在第一级继承时,就要把共同基类设置为虚基类
- 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。
八 多态
- 多态分为四种类型:重载多态、强制多态、包含多态、参数多态
- 静态绑定和动态绑定:
- 绑定过程出现在编译阶段,用对象名或者类名来限定要调用的函数**(重载、强制、参数)**
- 绑定过程工作在程序运行时执行,在程序运行时才确定将要调用的函数**(包含多态)**
- 一般有virtual就是动态绑定
静态绑定
double area(){};
动态绑定
virtual double area(){};