注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:02 掌握C++  

2012-12-30 14:11:33|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
(怀旧系列)VC程序设计(孙鑫老师)听课笔记:02 掌握C++ - jasonyang9 - jasonyang9的博客

=======
掌握C++
=======
C++特性:封装性、继承性、多态性。

封装性
------
在C++中,结构体(struct)中可以有函数,和类(class)基本通用。

用结构体(struct)封装变量和函数:

#include <iostream.h> // C++标准输入输出流头文件

struct Point
{
int x;
int y;
void output() // C++结构体中可以包含成员函数
{
cout << x << endl << y << endl;
}
};

void main()
{
Point pt;
pt.x = 5;
pt.y = 5;
// cout << pt.x << endl << pt.y << endl;
pt.output(); // 调用结构体中的成员函数进行输出
}



用类(class)封装变量和函数:

include <iostream.h>

class Point
{
public: // 必须将变量和函数声明为public,否则不能在外部访问这些成员
int x; // 成员变量
int y;
Point() // 构造函数
{
x = 0;
y = 0;
}
Point(int a, int b) // 重载的构造函数(声明对象时用类似Point pt(3, 3)的代码调用)
{
x = a;
y = b;
}
~Point() // 析构函数
{
}
void output() // 成员函数
{
cout << x << endl << y << endl;
}
};

void main()
{
Point pt; // 类Point实例化一个对象pt
pt.x = 5;
pt.y = 5;
pt.output();
}



结构体(struct)和类(class)的差异在于,struct默认成员函数的访问特性为公有(public),class默认成员函数的访问特性为私有(private)。
public:在派生类和外部都可以被访问;
protected:在派生类中可以被访问,在外部不能被访问;
private:在派生类和外部都不能被访问。

关于输入输出流:
C++提供了一套输入输出流类的对象,包括cin、cout和cerr,对应C语言中的三个文件指针stdin、stdout和stderr,分别指向终端输入、终端输出和标准出错输出(也从终端输出)。cin和>>一起完成输入操作,cout、cerr和<<一起完成输出和标准错误输出。利用cin和cout比C语言中的scanf和printf要方便,cin和cout可以自动判断输入输出数据类型,自动调整输入输出格式,不必像scanf和printf那样一个个由用户指定。使用cin、cout不仅方便,而且减少了出错的可能性。
对于输入,可以用以下方式:

int i;
cin >> i;



在输出中可以使用endl(end of line)表示换行,相当于C语言中的“\n”。

关于构造函数(constructor):
1、构造函数最重要的作用是创建对象本身(对象产生时内存的分配就是由构造函数完成的);
2、每个类必须(至少)有一个构造函数,否则就不能创建任何对象;
3、如果一个类没有提供任何构造函数,则C++提供一个默认的(由C++编译器提供)、不带参数的,只负责创建对象,不做任何初始化工作;
4、只要定义了一个构造函数(无论带不带参数),C++就不再提供默认的构造函数(即如果定义了一个带参的构造函数,还想要无参的,则必须自己定义)。

关于析构函数(destructor):
1、当一个对象生命周期结束时,其作占用的内存空间就要被回收,这个工作由析构函数来完成;
2、析构函数是“反向”的构造函数,不允许有返回值,不允许带参数,并且一个类中只能有一个析构函数;
3、析构函数的作用和构造函数相反,对象超出其作用范围,其内存空间被系统收回,或被程序用delete删除时,析构函数被调用;
4、可以在构造函数中初始化对象的某些成员变量,分配其内存空间(堆内存),在析构函数中释放对象运行期间所申请的资源。

CAnimal Animal(1, 2); // 带参构造函数
CAnimal Animal; // 无参构造函数



系统根据函数参数的个数和类型选择具体调用哪一个函数,称为“重载”。
注意:

void output();
int output();


这2个函数不能重载,编译器无法根据返回值判断调用哪一个。

void output(int a, int b = 5);
void output(int a);


这2个函数不能重载,由于存在默认参数(b = 5),例如调用output(1)时,可以解释为output(1)或output(1, 5),编译器无法判断调用哪一个函数。

关于“this指针”:
1、是一个隐含的指针,指向对象本身,代表了对象的地址(注意:是实例化后的对象,而不是类(class)。类(class)是一个抽象体);
2、一个类所有的对象调用的成员函数都是同一代码段,在对象调用成员函数时,除了实参外,还接受到了一个对象的地址,这个地址被一个隐含的形参this指针获取,等同于执行this=&对象变量,所有对数据成员的访问都隐含地加上前缀this->,例如:x=0;等价于this->x=0;。

“this指针”多用于形参和成员变量名称冲突时,区分到底对哪个变量进行操作,例如:

class Animal
{
private:
int x;
...
};

void Animal::Set(int x)
{
this->x = x; // 注意:指针用指向(“->”)操作符,而不是“.”
}



否则,如果代码是:

void Animal::Set(int x)
{
x = x; // 左侧的x是形参x,而不是Animal的成员变量x(即Animal的成员变量x不可见,除非用this->引出)
}



则Animal的成员变量没有被赋值。

小技巧:在MFC编程中,如果在成员函数中想调用同类中的某个成员,可利用VC提供的自动列出成员函数功能,输入this->,VC将列出该类中的所有成员,方便选择。

继承性
------

class Animal
{
public:
void eat()
{
cout << "animal eat" << endl;
}
void sleep()
{
cout << "animal sleep" << endl;
}
void breathe()
{
cout << "animal breathe" << endl;
}
};

class Fish : public Animal
{
// Fish类中没有任何成员函数
};

void main()
{
Fish fish;
fish.sleep(); // 但由于Fish类派生于Animal类,所以就有Fish类的sleep()等成员函数
}



类Fish继承自类Animal。注意这里采用了public继承方式。

关于继承方式和访问特性的关系:

基类的访问特性 类的继承方式 派生类的访问特性
------------------------------------------------
public public public
protected protected
private (NO ACCESS)
------------------------------------------------
public protected protected
protected protected
private (NO ACCESS)
------------------------------------------------
public private private
protected private
private (NO ACCESS)




关于基类和派生类构造、析构函数调用顺序:

class Animal
{
public:
Animal()
{
cout << "animal construct" << endl;
}
~Animal()
{
cout << "animal destruct" << endl;
}
};

class Fish : public Animal
{
public:
Fish()
{
cout << "fish construct" << endl;
}
~Fish()
{
cout << "fish destruct" << endl;
}
};

void main()
{
Fish fish;
}



输出应该是:

animal construct
fish construct
fish destruct
animal destruct



对于构造函数,先调用基类的,再调用派生类的;对于析构函数则相反,先调用派生类的,再调用基类的。
因为系统先构造基类对象,后构造派生类对象;先销毁派生类对象,后销毁基类对象。


关于在派生类中向基类构造函数传参:

class Animal
{
public:
Animal(int height, int weight)
{
cout << "animal construct" << endl;
}
~Animal()
{
cout << "animal destruct" << endl;
}
};

class Fish : public Animal
{
public:
Fish() : Animal(400, 300), a(1) // 向基类带参构造函数传参,否则编译不通过,因为基类没有默认无参构造函数
// 或可对常量(a)进行初始化
{
cout << "fish construct" << endl;
}
~Fish()
{
cout << "fish destruct" << endl;
}
private:
const int a;
};

void main()
{
Fish fish;
}




关于函数的覆盖:函数的覆盖发生在基类和派生类之间。

class Animal
{
public:
void breathe()
{
cout << "animal breathe" << endl;
}
};

class Fish : public Animal
{
public:
void breathe()
{
// Animal::breathe(); // “::”称为作用域符,表示要调用Animal类中的方法
cout << "fish bubble" << endl;
}
};

void main()
{
Fish fish;
fish.breathe();
}



输出应该是:

fish bubble


因为派生类的成员函数名称、参数个数和类型同基类中的完全一致,发生了覆盖。用于派生类将基类中的某些行为替代掉。


(进阶)关于对象类型转换

class Animal
{
public:
void breathe()
{
cout << "animal breathe" << endl;
}
};

class Fish : public Animal
{
public:
void breathe()
{
cout << "fish bubble" << endl;
}
};

void fn(Animal *pAn)
{
pAn->breathe();
}

void main()
{
Fish fish; // 构造fish对象
Animal *pAn; // 定义pAn指针
pAn = &fish; // 将fish对象的地址赋值给pAn(注意:这里有一个隐含的类型转换!)
fn(pAn); // 调用fn()函数
}



这里的输出应该是:

animal breathe



不同变量间能否进行转换要看它们的内存模型是否匹配。
其次,如果将数据长度大的类型转换为数据长度小的类型,会发生精度的丢失!

char ch;
int i;

ch = (char) i; // 精度丢失!i占用4个字节,而char只有1个字节,则只有第1个字节被保留,其余3个丢失!
i = (int) ch; // 无精度丢失



因为,Fish类对象基于Animal类对象,含有附加信息。将Fish类对象转换为Animal类对象时会丢失精度,而且由于基类和派生类对象内存模型匹配,也就访问到了基类的方法。


多态性
------
当C++编译器在编译时,如果基类的成员函数为虚函数,则采用迟绑定(Late Binding)技术,在运行时根据对象的类型来确认调用哪一个函数。
即基类中用virtual关键字定义虚函数后,如果派生类中有此函数就会调用派生类函数,否则调用基类中的函数。

class Animal
{
public:
virtual void breathe()
{
cout << "animal breathe" << endl;
}
};

class Fish : public Animal
{
public:
void breathe()
{
cout << "fish bubble" << endl;
}
};

void fn(Animal *pAn)
{
pAn->breathe();
}

void main()
{
Fish fish; // 构造fish对象
Animal *pAn; // 定义pAn指针
pAn = &fish; // 将fish对象的地址赋值给pAn(注意:这里有一个隐含的类型转换!)
fn(pAn); // 调用fn()函数
}



这里的输出应该是:

fish bubble



纯虚函数(virtual func() = 0;)表示该基类不可被实例化,这种基类称为抽象类,必须由派生类实现此函数后才能实例化对象。


关于“引用”和“指针”
----------------------

int a = 5;
int &b = a; // b必须在定义时赋值(&符号在定义变量时表示应用,否则表示取地址,如int *p; p = &a;表示取a的地址给指针p)


表示“b”为“a”的引用,“b”不占用内存,直接指向“a”的空间。所以,“引用”又称为“别名”。
“引用”多用于MFC传参,让传递的参数可以被函数修改后将结果返回。例如:

int exchange(int &a, int &b) // 在exhange()函数中修改的a和b的值后,能将结果返回到调用函数
{
int temp;
temp = a;
a = b;
b = temp;
}

void main()
{
int x, y;
x = 5;
y = 6;

exchange(x, y); // 调用后,x和y值互换
}




int a = 5;
int *pA = &a; // 取a地址赋值给pA


表示“pA”为指向“a”地址的指针,“pA”本身需要占用内存。


关于头文件(.h)和源文件(.cpp)
-------------------------------
将函数定义和实现分离,对类同样适用。

#include "Animal.h"


包含头文件时,用双引号(")包含,从当前目录开始查找。

#include <iostream.h>


用尖括号(<>)包含,从系统目录开始查找。

为了避免重复定义类(或函数),可用“预编译指令符”:

在animal.h中:

#ifndef ANIMAL_H
#define ANIMAL_H
#endif
...



在main.cpp(或所有用到Animal类的源文件中):

#ifndef ANIMAL_H
#include "animal.h"
#endif
...



VC程序编译链接原理和过程:
1、预处理.cpp和.h文件;(“预编译指令符”在这里被处理)
2、编译器接受预处理输出,将源代码编译成目标文件.obj;(每个文件单独编译)
3、链接器将目标文件.obj和标准库函数标准类库.lib链接成.exe文件。

  评论这张
 
阅读(174)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017