文章目录
一、常引用
定义引用时,前面加const关键字,即为“常引用”
例如:
int n;
const int & r = n;
r 的类型是 const int &
注意:
- 不能通过常引用去修改其引用的内容
int n = 100;
const int & r = n;
r = 200; //编译错
n = 300; // 没问题
-
const T & 和T & 是不同的类型!!!
T & 类型的引用或T类型的变量可以用来初始化const T & 类型的引用。
const T 类型的常变量和const T & 类型的引用则不能用来初始化T &类型的引用,除非进行强制类型转换。 -
“const”关键字的用法
定义常量:
const int MAX_VAL = 23;
const string SCHOOL_NAME = “Peking University”;
定义常量指针:
不可通过常量指针修改其指向的内容
int n,m;
const int * p = & n;
* p = 5; //编译出错
n = 4; //ok
p = &m; //ok, 常量指针的指向可以变化
不能把常量指针赋值给非常量指针,反过来可以
const int * p1; int * p2;
p1 = p2; //ok
p2 = p1; //error
p2 = (int * ) p1; //ok,强制类型转换
函数参数为常量指针时,可避免函数内部不小心改变参数指针所指地方的内容
void MyPrintf( const char * p )
{
strcpy( p,"this"); //编译出错
printf("%s",p); //ok
}
定义常引用:
不能通过常引用修改其引用的变量
int n;
const int & r = n;
r = 5; //error
n = 4; //ok
二、动态内存分配
用new 运算符实现动态内存分配
1.用法一(分配一个变量)
P = new T;
T是任意类型名,P是类型为T * 的指针。
动态分配出一片大小为 sizeof(T)字节的内存空间,并且将该
内存空间的起始地址赋值给P。比如:
int * pn;
pn = new int;
* pn = 5;
2.用法二(分配一个数组)
P = new T[N];
T :任意类型名
P :类型为T * 的指针
N :要分配的数组元素的个数,可以是整型表达式
动态分配出一片大小为 sizeof(T)*N字节的内存空间,并
且将该内存空间的起始地址赋值给P。
动态分配数组示例:
int * pn;
int i = 5;
pn = new int[i * 20];
pn[0] = 20;
pn[100] = 30; //编译没问题。运行时导致数组越界
3.delete运算符
作用:释放动态分配的内存
用“new”动态分配的内存空间,一定要用“delete”运算符进行释放
用“delete”释放动态分配的数组,要加“[]”
用法:
- delete 指针;//该指针必须指向new出来的空间
例如:
int * p = new int;
* p = 5;
delete p;
delete p; //导致异常,一片空间不能被delete多次
- delete [ ] 指针;//该指针必须指向new出来的数组
int * p = new int[20];
p[0] = 1;
delete [ ] p;
三、函数
1.内联函数(inline)
- 函数调用是有时间开销的。如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。
- 为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
inline int Max(int a,int b)
{
if( a > b) return a;
return b;
}
2.函数重载
-
一个或多个函数,名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。
以下三个函数是重载关系: int Max(double f1,double f2) { } int Max(int n1,int n2) { } int Max(int n1,int n2,int n3) { } 函数重载使得函数命名变得简单。 编译器根据调用语句的中的实参的个数和类型判断应该调用哪个函数。 Max(3.4,2.5); //调用 (1) Max(2,4); //调用 (2) Max(1,2,3); //调用 (3) Max(3,2.4); //error,二义性
3.函数缺省参数
- C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。
void func( int x1, int x2 = 2, int x3 = 3)
{ }
func(10 ) ; //等效于 func(10,2,3)
func(10,8) ; //等效于 func(10,8,3)
func(10, , 8) ; //不行,只能最右边的连续若干个参数缺省
- 函数参数可缺省的目的在于提高程序的可扩充性。
- 即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。
四、面向对象程序设计
-
面向对象的程序设计方法,能够较好解决上述问题。
面向对象的程序 = 类 + 类 + …+ 类 -
设计程序的过程,就是设计类的过程。
-
面向对象的程序设计方法:
将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性); 将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构(这一步叫“抽象”)。
-
然后,通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封装”。
-
面向对象的程序设计具有“抽象”,“封装”“继承”“多态”四个基本特点。
五、类成员的可访问范围
- 在类的定义中,用下列访问范围关键字来说明类成员
可被访问的范围:
– private: 私有成员,只能在成员函数内访问
– public : 公有成员,可以在任何地方访问
– protected: 保护成员,以后再说 - 以上三种关键字出现的次数和先后次序都没有限制。
- 定义一个类
class className {
private:
私有属性和函数
public:
公有属性和函数
protected:
保护属性和函数
};
- 如过某个成员前面没有上述关键字,则缺省地被认为是私有成员。
- 在类的成员函数内部,能够访问:
– 当前对象的全部属性、函数;
– 同类其它对象的全部属性、函数。 - 在类的成员函数以外的地方,只能够访问该类对象的公有成员。
- 设置私有成员的机制,叫“隐藏”
“隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。 - “隐藏”的作用:防止数组越界,可以在私有化的方法限制相关输入,确保程序里面不越界。
- struct和class定义类的区别:
和用"class"的唯一区别,就是未说明是公有还是私有的成员,就是公有
六、成员函数的重载及参数缺省
成员函数也可以重载
成员函数可以带缺省参数。
使用缺省参数要注意避免有函数重载时的二义性
class Location {
private :
int x, y;
public:
void init( int x =0, int y = 0 );
void valueX( int val = 0) { x = val; }
int valueX() { return x; }
};
Location A;
A.valueX(); //错误,编译器无法判断调用哪个valueX
七、构造函数(constructor)
1.定义
-
成员函数的一种
名字与类名相同,可以有参数,不能有返回值(void也不行) 作用是对对象进行初始化,如给成员变量赋初值 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数 默认构造函数无参数,不做任何操作
-
如果定义了构造函数,则编译器不生成默认的无参数的构造函数
-
对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
-
一个类可以有多个构造函数
-
为什么需要构造函数:
构造函数执行必要的初始化工作,有了构造函数,就不必专门再写初始化函数,也不用担心忘记调用初始化函数。 有时对象没被初始化就使用,会导致程序出错。
-
可以有多个构造函数,参数个数或类型不同
-
构造函数最好是public的,private构造函数不能直接用来初始化对象
class Complex {
private :
double real, imag;
public:
Complex( double r, double i = 0);
Complex (double r );
Complex (Complex c1, Complex c2);
};
Complex::Complex( double r, double i) {
real = r; imag = i;
}
Complex::Complex(double r)
{
real = r; imag = 0;
}
Complex::Complex (Complex c1, Complex c2);
{
real = c1.real+c2.real;
imag = c1.imag+c2.imag;
}
Complex c1; // error, 缺少构造函数的参数
Complex * pc = new Complex; // error, 没有参数
Complex c1(2); // OK
Complex c1(2,4), c2(3,5);
Complex * pc = new Complex(3,4);
2.构造函数在数组中的使用
如果是数组形式初始化,数组长度是几,构造函数被初始化几次。
class CSample {
int x;
public:
CSample() {
cout << "Constructor 1 Called" << endl;
}
CSample(int n) {
x = n;
cout << "Constructor 2 Called" << endl;
}
};
int main(){
CSample array1[2];
cout << "step1"<<endl;
CSample array2[2] = {4,5};
cout << "step2"<<endl;
CSample array3[2] = {3};
cout << "step3"<<endl;
CSample * array4 =
new CSample[2];
delete []array4;
return 0;
}
输出:
Constructor 1 Called
Constructor 1 Called
step1
Constructor 2 Called
Constructor 2 Called
step2
Constructor 2 Called
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called
八、复制构造函数copy constructor
1.定义
- 只有一个参数,即对同类对象的引用。
- 形如 X::X( X& )或X::X(const X &), 二者选一后者能以常量对象作为参数
- 如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
class Complex {
private :
double real,imag;
};
Complex c1; //调用缺省无参构造函数
Complex c2(c1);//调用缺省的复制构造函数,将 c2 初始化成和c1一样
- 如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
class Complex {
public :
double real,imag;
Complex(){ }
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called
- 不允许有形如 X::X( X )的构造函数。
class CSample {
CSample( CSample c ) {
} //错,不允许这样的构造函数
};
2.复制构造函数起作用的三种情况
- 当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句 - 如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用。
class A
{
public:
A() { };
A( A & a) {
cout << "Copy constructor called" <<endl;
}
};
void Func(A a1){ }
int main(){
A a2;
Func(a2);
return 0;
}
程序输出结果为: Copy constructor called
- 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:
class A
{
public:
int v;
A(int n) { v = n; };
A( const A & a) {
v = a.v;
cout << "Copy constructor called" <<endl;
}
};
A Func() {
A b(4);
return b;
}
int main() {
cout << Func().v << endl; return 0;
}
输出结果:
Copy constructor called
4
注意:对象间赋值并不导致复制构造函数被调用
3.引常量引用参数的使用
void fun(CMyclass obj_ ) {
cout << “fun” << endl;
}
- 这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
- 所以可以考虑使用 CMyclass & 引用类型作为参数。
- 如果希望确保实参的值在函数中不应被改变,那么可以加上const 关键字:
void fun(const CMyclass & obj) {
//函数中任何试图改变 obj值的语句都将是变成非法
}
九、类型转换构造函数
1.定义
- 定义转换构造函数的目的是实现类型的自动转换。
- 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
- 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
2.实例
class Complex {
public:
double real, imag;
Complex( int i) {//类型转换构造函数
cout << "IntConstructor called" << endl;
real = i; imag = 0;
}
Complex(double r,double i) {real = r; imag = i; }
};
int main ()
{
Complex c1(7,8);
Complex c2 = 12;
c1 = 9; // 9被自动转换成一个临时Complex对象
cout << c1.real << "," << c1.imag << endl;
return 0;
}
十、析构函数destructors
1.定义
- 名字与类名相同,在前面加‘~’, 没有参数和返回值,一个类最多只能有一个析构函数。
- 析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
- 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
- 如果定义了析构函数,则编译器不生成缺省析构函数。
2.析构函数和数组
- 对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。
class String{
private :
char * p;
public:
String () {
p = new char[10];
}
~ String () ;
};
String ::~ String()
{
delete [] p;
}
class Ctest {
public:
~Ctest() { cout<< "destructor called" << endl; }
};
int main () {
Ctest array[2];
cout << "End Main" << endl;
return 0;
}
输出:
End Main
destructor called
destructor called
- delete 运算导致析构函数调用。
- 若new一个对象数组,那么用delete释放时应该写 []。否则只delete一个对象(调用一次析构函数)
Ctest * pTest;
pTest = new Ctest; //构造函数调用
delete pTest; //析构函数调用
---------------------------------------------------------
pTest = new Ctest[3]; //构造函数调用3次
delete [] pTest; //析构函数调用3次
十一、this指针
1.定义
- 其作用就是指向成员函数所作用的对象
- 非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。
class Complex {
public:
double real, imag;
void Print() { cout << real << "," << imag ; }
Complex(double r,double i):real(r),imag(i)
{ }
Complex AddOne() {
this->real ++; //等价于 real ++;
this->Print(); //等价于 Print
return * this;
}
};
int main() {
Complex c1(1,1),c2(0,0);
c2 = c1.AddOne();
return 0;
} //输出 2,1
2.this指针和静态成员函数
- 静态成员函数中不能使用 this 指针!
- 因为静态成员函数并不具体作用与某个对象!
- 因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数!
十二、静态成员
1.定义
- 在说明前面加了static关键字的成员。
- 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
sizeof 运算符不会计算静态成员变量。
class CMyclass {
int n;
static int s;
};
sizeof( CMyclass ) 等于 4
- 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
- 因此静态成员不需要通过对象就能访问。
- 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
- 静态成员函数本质上是全局函数。
- 设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
- 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
class CRectangle
{
private:
int w, h;
static int nTotalArea; //静态成员变量
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal(); //静态成员函数
};
2.访问方式
- 类名::成员名
CRectangle::PrintTotal();
- 对象名.成员名
CRectangle r; r.PrintTotal();
- 指针->成员名
CRectangle * p = &r; p->PrintTotal();
- 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
十三、成员对象和封闭类
1.定义
- 有成员对象的类叫 封闭(enclosing)类。
class CTyre //轮胎类
{
private:
int radius; //半径
int width; //宽度
public:
CTyre(int r,int w):radius(r),width(w) { }
};
class CEngine //引擎类
{
};
class CCar { //汽车类
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p,int tr,int tw );
};
CCar::CCar(int p,int tr,int w):price(p),tyre(tr, w)
{
};
int main()
{
CCar car(20000,17,225);
return 0;
}
上例中,如果 CCar类不定义构造函数, 则下面的语句会编译出错:CCar car;
因为编译器不明白 car.tyre该如何初始化。car.engine 的初始化没问题,用默认构造函数即可。
任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的。
具体的做法就是:通过封闭类的构造函数的初始化列表。
成员对象初始化列表中的参数可以是任意复杂的表达式,可以包括函数,变量,只要表达式中的函数或变量有定义就行。
2.封闭类构造函数和析构函数的执行顺序
- 封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数。
- 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
- 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。
class CTyre {
public:
CTyre() { cout << "CTyre contructor" << endl; }
~CTyre() { cout << "CTyre destructor" << endl; }
};
class CEngine {
public:
CEngine() { cout << "CEngine contructor" << endl; }
~CEngine() { cout << "CEngine destructor" << endl; }
};
class CCar {
private:
CEngine engine;
CTyre tyre;
public:
CCar( ) { cout << “CCar contructor” << endl; }
~CCar() { cout << "CCar destructor" << endl; }
};
int main(){
CCar car;
return 0;
}
输出结果:
CEngine contructor
CTyre contructor
CCar contructor
CCar destructor
CTyre destructor
CEngine destructor
初始化是执行成员对象的构造函数,在执行本体的构造函数,象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数,次序和构造函数的调用次序相反。
3.封闭类的复制构造函数
封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象,也会用复制构造函数初始化。
class A
{
public:
A() { cout << "default" << endl; }
A(A & a) { cout << "copy" << endl;}
};
class B { A a; };
int main()
{
B b1,b2(b1);
return 0;
}
输出:
default
Copy
说明b2.a是用类A的
复制构造函数初始化的
。而且调用复制构造函
数时的实参就是b1.a。
十四、友元(friends)
友元分为友元函数和友元类两种
1. 友元函数
一个类的友元函数可以访问该类的私有成员.
class CCar ; //提前声明 CCar类,以便后面的CDriver类使用
class CDriver
{
public:
void ModifyCar( CCar * pCar) ; //改装汽车
};
class CCar
{
private:
int price;
friend int MostExpensiveCar( CCar cars[], int total); //声明友元
friend void CDriver::ModifyCar(CCar * pCar); //声明友元
};
void CDriver::ModifyCar( CCar * pCar)
{
pCar->price += 1000; //汽车改装后价值增加
}
int MostExpensiveCar( CCar cars[],int total)
//求最贵汽车的价格
{
int tmpMax = -1;
for( int i = 0;i < total; ++i )
if( cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}
int main()
{
return 0;
}
可以将一个类的成员函数(包括构造、析构函数)说明为另一个类的友元。
class B {
public:
void function();
};
class A {
friend void B::function();
};
2.友元类
如果A是B的友元类,那么A的成员函数可以访问B的私有成员。
友元类之间的关系不能传递,不能继承
{
private:
int price;
friend class CDriver; //声明CDriver为友元类
};
class CDriver
{
public:
CCar myCar;
void ModifyCar() {//改装汽车
myCar.price += 1000;//因CDriver是CCar的友元类,故此处可以访问其私有成员
}
};
十五、常量成员函数
1.定义
- 如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加 const关键字。
class Sample {
private :
int value;
public:
Sample() { }
void SetValue() { }
};
const Sample Obj; // 常量对象
Obj.SetValue (); //错误 。常量对象只能使用构造函数、析构函数和 有const 说明的函数(常量方法)
- 在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
- 常量成员函数内部不能改变属性的值,也不能调用非常量成员函数。
- 在定义常量成员函数和声明常量成员函数时都应该使用const 关键字。
class Sample {
private :
int value;
public:
void PrintValue() const;
};
void Sample::PrintValue() const { //此处不使用const会
//导致编译出错
cout << value;
}
void Print(const Sample & o) {
o.PrintValue(); //若 PrintValue非const则编译错
}
2.常量成员函数
- 如果一个成员函数中没有调用非常量成员函数,也没有修改成员变量的值,那么,最好将其写成常量成员函数。
- 常量成员函数的重载
两个函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
#include <iostream>
using namespace std;
class CTest {
private :
int n;
public:
CTest() { n = 1 ; }
int GetValue() const { return n ; }
int GetValue() { return 2 * n ; }
};
int main() {
const CTest objTest1;
CTest objTest2;
cout << objTest1.GetValue() << "," <<
objTest2.GetValue() ;
return 0;
}
=>1,2
3.mutable成员变量
可以在const成员函数中修改的成员变量
class CTest
{
public:
bool GetData() const
{
m_n1++;
return m_b2;
}
private:
mutable int m_n1;
bool m_b2;
};