关于C/C++的一些思考(2)
C++引入类机制的目的:
从语法上将数据和操作捆绑在一起;
从语法上消除变量和函数的名字冲突;
从语法上允许服务端设计者控制数据和函数的访问权限;
从工程上支持数据封装、信息隐藏、将责任推向服务端、减小信息共享、独立问题域,减少信息的交换量,减少程序员之间的协调;
C++和C定义结构的区别:
C++中struct和class除了前者的缺省访问限制符为public,后者为private,继承关系中前者缺省为public,后者缺省为private之外,其他都相同;
C中的struct不能定义方法,表示一个纯数据结构,不能有嵌套函数,只能定义变量,但可以定义函数指针变量,从而具有模拟C++类方法的能力;
C++中常见类型的声明:(注意:[]的优先级大于*)
整型数:int a;
指向整型数的指针:int *a;
指向一个指向整形数的指针的指针:int **a;
数组元素为10个整型数的数组:int a[10];
数组元素为10个指向整形数的指针的数组:int *a[10];
指向一个由10个整型数组成的数组的指针:int (*a)[10];
指向一个有一个整型数参数,返回值为一个整型数的函数的指针:
int (*a)(int );
数组元素为10个上述函数指针类型的数组:int (*a[10])(int );
C++空类(没有定义任何变量和函数)中,默认产生的六种类成员函数:
class Empty
{
public:
Empty(); // 无参构造函数
~Empty(); // 析构函数
Empty( const Empty& ); // 拷贝构造函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 常量取址运算符const
};
这样的对象在内存中有一个字节(1byte)的占位符,如果有虚拟指针,则另外增加4个字节的内存耗用,一共5个字节;
注意到有两个缺省的取值运算符,这样的设计时为了应对常量对象的声明;所以类定义中如果定义int getX();则应该定义对应的int getX() const重载函数(缺省类参数为const *this),以防止当申明常量对象时调用int getX()出现错误;
缺省构造函数(Default Constructor):
如果类定义中没有显示定义任何构造函数,则编译器会自动为我们生成一个缺省构造函数,也就是无参构造函数;但是,一旦我们定义任何类型的构造函数后,编译器不会自动生成缺省构造函数;
Cylinder(){……}
Cylinder c; Cylinder c[100]; Matrix {Cylinder c; ……};
如果我们在某类定义中定义了非无参构造函数,当此类型变量作为另一个类d的类成员,创建d类型的时候需要首先初始化其类成员,此时c需要一个无参构造函 数,但此时编译器不再自动生成,所以会出现错误;另外一种错误情况就是定义Cylinder c[100];的时候;所以好的类设计总会提供一个无参构造函数;
Cylinder cl;会隐式调用缺省(无参)构造函数;但是调用malloc不会隐式调用任何构造函数;仅仅是在堆内存申请一块内存,并且也是C++中唯一可以不调用构造函数而创建对象的方法;
Cylinder *c2=(Cylinder*)malloc(sizeof(Cylinder));
良好的构造函数设计:
class Point {…… Point(int a=0, int b=0):size(a),height(b) {……}};
上述类定义中的构造函数可实现四个功能:
如果创建对象时用户不传任何参数Point p;则可作为缺省无参构造函数;
如果创建对象时用户仅提供一个参数Point p=20;Point p(10);则作为转换构造函数;
如果创建对象时用户提供两个参数Point p(10,28);则作为一般构造函数;
如果成员变量为对象,则成员初始化列表可以让成员仅初始化一次,提升程序性能;
拷贝构造函数(Copy Constructor):
无论用户是否定义了其他的构造函数,编译器都会自动生成一个拷贝构造函数,除非用户显示定义自己的拷贝构造函数;
但系统提供的拷贝构造函数在初始化拷贝时仅仅是将数据成员进行值复制(也就是浅复制),如果实例对象中有索引动态内存的指针变量,则拷贝前后会共享同一动态内存,此时需要特别注意动态内存的关系(这种情况下一般都需要重新定义拷贝构造函数)
Cylinder(const Cylinder & copy) {……}
Cylinder c1(1,3); Cylinder c2=c1; Cylinder c3(c1);
如果定义为按值传递,则会造成调用结构上的无限递归;函数调用过程中编译器会使用实际参数值初始化形式参数,也就是调用拷贝构造函数,所以最终导致无限递归调用;
另外注意区分拷贝构造函数和赋值操作符重载函数,前者在对象初始化的时候调用,后者在对象初始化之后调用,分别调用不同的函数;
Cylinder c1(1,3); Cylinder c2=c1; Cylinder c3; c3=c2;
转换构造函数(Conversion Constructor):
需要用户显式定义,编译器不会自动提供;此类构造函数有且只能有一个参数(特别像一般的构造函数),并且为非本类类型,则在调用函数的时候可写作,Object o(30);也可写作:Object c=30;
Cylinder(int n) {……};
首先调用转换构造函数利用30生成一个临时的匿名Object对象,然后使用拷贝构造函数或者赋值操作符重载函数进行与c的赋值操作;
Cylinder c1=20; Cylinder c2; c2=10;
所以使用内置基本类型对用户自定义类型进行初始化或者赋值时,编译器会首先检查这个自定义类型是否具有转换构造函数,然后再判断是否是语法错误;
如果某个函数的形式参数为Object类型,但仅提供一个int类型的实际参数,此时如果Object类定义中定义了针对int类型的转换构造函数,则转换过程如下:
1). 编译器先用转换构造函数将int类型的值生成一个临时的匿名Object对象;
2). 如果是其他数值类型(如double),编译器还会首先进行位提升或者类型强制转化(double->int,就是针对其他类型和转换构造函数的参数类型),之后再调用转换构造函数生成一个匿名的临时Object对象;
3). 最后将这个临时的Object对象作为真正的参数传入函数;
Cylinder(int n) {……};
void Copy(Cylinder *to, Cylinder &from) {……}
Cylinder c1; Copy(&c1,70.8);
注意参数不能是指针类型,因为转换构造函数创建的是一个变量,而不是指针;
析构函数(Destruction Constructor):
用于释放实例类在运行过程中申请的系统资源,比如堆内存;析构函数没有参数,没有返回值,有且仅有一个;如果没有显示定义则编译器会提供一个什么也不做的 缺省析构函数;析构函数调用的顺序需要和构造函数调用的顺序相反,比如类成员中的对象需要优先调用自己的析构函数,继承关系中也需要让基类对象优先调用析 构函数;
调用析构函数的情景,其作用是在对象的内存空间被撤销之前做一些清理工作;
1). 对于extern和static变量,在main函数结束之后,程序结束之前;2). 对于auto变量,在作用域结束之前;
3). 对于heap变量,new/delete类型的话在delete的时候;malloc/free类型的话在free函数调用之前需要手动调用析构函数;
虚函数设计中需注意的问题:
虚拟函数利用多态机制基于同一调用接口实现不同的功能;
如果子类定义一个与父类中虚函数仅仅名字相同的函数(参数不同,返回类型不同),则不能产生多态机制,而仅仅是函数覆盖;(也就是说虚函数必须要求函数名和参数都相同,并且基类的函数标注virtual);
虚函数实现依赖vtable,而vtable与实例对象绑定(this指针),所以仅有类成员函数才可声明为虚函数;全局函数,构造函数,static和inline修饰的函数则不能:
static函数独立于实例对象;
inline函数在编译时期就将函数声明替换为方法体;
constructor函数调用的时候并不存在完整的对象;当调用构造函数的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定,不能是虚函数;而析构函数可以是虚函数;
如果不能保证某个类不会有子类,一般需要将destructor函数定义为虚函数;
vtable在编译期就已经确定并与类定义绑定,实例对象则含有指向VT的指针;
Const关键字:
修饰变量:此变量的值在定义时必须初始化,之后不可再赋值;甚至不可将其地址赋值给其他指针变量,仅当指针变量和引用变量前使用const时才可,此指针和引用变量不可用作左值;
用于一般变量申明:表示常量变量,仅能在声明的时候赋值;
用于参数申明:表示参数作为输入值,否则参数作为输出值;
用于函数的返回值:表示返回值不能用于左值(不能改变);
修饰函数:在函数名之后,{}之前添加const表示函数内部不会修改参数的值;如果此函数为类成员函数则说明不会修改所有成员变量的值;
用于一般类成员函数申明(添加于函数末尾):表示此函数不修改此类的成员变量(const this的指针)
class Cylinder {…… void show() const {……}};
Point* colsestOne(const Point &p) {return …… this :
const_cast<Point*>(&p);}
使用const_cast<Type>(ConstType)可以将同类型的由const修饰的变量转换成没有const属性的变量,但仅去除常量属性仍旧不能修改变量的值;
各种程序设计机制存在的用意:
用static修饰的类成员函数:此函数与类实例对象相互独立,所以完全可为全局函数,但为了从语法层面表明与某个类别相关,则在类定义内部实现,方便传达程序员的意图;可以直接使用类名调用,也可以使用对象调用;
Point::samePoint(p1, p2);
用const修饰的类成员函数:此函数不改变类对象成员的成员变量(全局函数则不改变参数变量),传入const *this指针;使用const修饰传入参数,则表示此参数为输入值,不可改变,否则为输出值,可以改变;
void show() const {……}
用Rational(long n=0, long d=1);定义构造函数:这个构造函数可以作为一般构造函数,转换构造函数,缺省构造函数,减少代码量;转换构造函数的调用通常为隐式行为,如果我们要求 类型转换需要显式执行,则在这个构造函数函数前添加explicit,则仅当使用强制转换操作时才能调用此函数,不能隐式转换;
用friend Rational operator+(const Rational &x, const Rational &y)定义友元函数:具有和类成员函数一样的访问权限,但编译器不为其缺省加入this指针,一般为全局函数或者另一个类定义的函数。用于解决重 载运算符函数中,左操作数为数值类型,右操作数为对象类型的情况(一般的类的重载运算符函数中左操作数必须为对象本身,也就是缺省的this),当左操作 数为数值类型时,首先调用类型转换函数将其变成类类型,然后调用对应的类重载操作符函数;
使用成员初始化列表:在实现构造函数时在构造函数头和函数体之间加入定义,有4个方面的优势:使用成员初始化列表:在实现构造函数时在构造函数头和函数体之间加入定义,有4个方面的优势:
可以避免类成员变量为对象变量时进行的不必要的缺省构造函数的调用(初始化列表并没有规定变量的初始化顺序,变量在类定义中的顺序决定其初始化顺序),提升性能;
可以避免因为类变量定义中没有定义缺省构造函数而出现语法错误;
可以避免const变量的错误初始化,类成员中的const变量由于只能在定义时初始化,所以需要使用初始化列表进行初始化;
可以避免引用类型变量的错误初始化,类成员中的引用类型变量也只能在定义时初始化,所以需要使用初始化列表进行初始化;
类定义中总是提供缺省构造函数:防止类型以无参数方式创建时发生错误(数组元素或者类成员变量);
将拷贝构造函数声明为private:禁止按值传递此类对象;也可将其他对象的函数声明为private,达到对应的限制;
关于C/C++的一些思考(2)的更多相关文章
- 领域驱动和MVVM应用于UWP开发的一些思考
领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...
- 关于面试题 Array.indexof() 方法的实现及思考
这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...
- 关于 CSS 反射倒影的研究思考
原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...
- 关于.NET参数传递方式的思考
年关将近,整个人已经没有了工作和写作的激情,估计这个时候很多人跟我差不多,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有很多想法,但就是叫不动我的手脚,所以我只能看着别人在做我想做 ...
- 使用NUnit为游戏项目编写高质量单元测试的思考
0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...
- OpenGL shader 中关于顶点坐标值的思考
今天工作中需要做一个事情: 在shader内部做一些空间距离上的计算,而且需要对所有的点进行计算,符合条件的显示,不符合条件的点不显示. 思路很简单,在vertex shader内知道顶点坐标,进行计 ...
- 关于领域驱动设计(DDD)中聚合设计的一些思考
关于DDD的理论知识总结,可参考这篇文章. DDD社区官网上一篇关于聚合设计的几个原则的简单讨论: 文章地址:http://dddcommunity.org/library/vernon_2011/, ...
- 关于bug分析与异常处理的一些思考
前言:工作三年了,工作内容主要是嵌入式软件开发和维护,用的语言是C,毕业后先在一家工业自动化控制公司工作两年半,目前在一家医疗仪器公司担任嵌入式软件开发工作.软件开发中,难免不产生bug:产品交付客户 ...
- 【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。
× 缘起---闲逛博客园 前几天的时候,在某一QQ群看到一条消息“XXX酒店开房XXXBTXX迅雷BT下载”,当时是一目十行的心态浏览,目光掠过时, 第一反应我想多了~以为是XX种子(你懂的~ ...
- 对于多个数据库表对应一个Model问题的思考
最近做项目遇到一个场景,就是客户要求为其下属的每一个分支机构建一个表存储相关数据,而这些表的结构都是一样的,只是分属于不同的机构.这个问题抽象一下就是多个数据库表对应一个Model(或者叫实体类).有 ...
随机推荐
- hdu4975 A simple Gaussian elimination problem.(最大流+判环)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4975 题意:和hdu4888基本一样( http://www.cnblogs.com/a-clown/ ...
- Centos6可以ping通但浏览器不能上网(解决)
遇到这种情况,只需要修改一下DNS Server即可,如下图. 这样再试试,应该可以了吧-
- MySQL(调优慢查询、explain profile) 转
转自http://www.linuxidc.com/Linux/2012-09/70459.htm mysql profile explain slow_query_log分析优化查询 在做性能测试中 ...
- 立体渲染 Volumetric Rendering
基础概念 在3D游戏引擎中,球体.立方体以及所有其它复杂的集合体都是由三角面片组成的.引擎只会渲染物体的表面,比如球体,半透明物体等.整个世界由各种空壳构成. 立体渲染(Volumetric Rend ...
- bzoj 2792: [Poi2012]Well【二分+贪心】
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const ...
- javascript匿名方法
首先,看一段很有意思的代码: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> < ...
- php安装的扩展php -m可以看到,但是phpinfo()看不到,php-fpm关闭了重新打开还是不行?
问答 问答详情 php安装的扩展php -m可以看到,但是phpinfo()看不到,php-fpm关闭了重新打开还是不行? centos apache linux html php 3.2k 次浏 ...
- 数据结构 - 链队列的实行(C语言)
数据结构-链队列的实现 1 链队列的定义 队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已, 我们把它简称为链队列.为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指 ...
- CF1119F Niyaz and Small Degrees
题意 给你\(n\)个点的树,边有边权 问使得所有的点度数都小于等于\(x\)的最小删边的代价 \([x \in 0...n-1]\) 题解 首先对于每个\(x\) 可以有一个\(O(nlogn)\) ...
- 洛谷 P2056 [ZJOI2007]捉迷藏 || bzoj 1095: [ZJOI2007]Hide 捉迷藏 || 洛谷 P4115 Qtree4 || SP2666 QTREE4 - Query on a tree IV
意识到一点:在进行点分治时,每一个点都会作为某一级重心出现,且任意一点只作为重心恰好一次.因此原树上任意一个节点都会出现在点分树上,且是恰好一次 https://www.cnblogs.com/zzq ...