关于C/C++的一些思考(1)
C++的前世今生:
- C的结构化思想;
- Ada的模版思想;
- Fortran的运算符重载思想;
- Simula的OO思想:封装,继承,多态;
C++类型描述了变量的三个特征:
- 该类型在内存中占用物理空间的大小(空间读取范围);
- 该类型的值的合法的取值范围(位模式解释方法);
- 合法的操作集(数据的用法);
C++的整数类型有两类修饰符(Qualifier):
signed和unsigned:调整整型数值的取值范围(不改变内存大小,仅改变数值读取模式,unsigned表示读取模式为非负数);整数类型 (int)缺省为signed int(signed和unsigned可以与short和long一起使用,修饰符顺序没有关系);
short和long:调整整型数值的物理空间大小(改变内存大小,不改变数值读取模式,目的是节省空间耗用或者增大取值范围);一般平台下的short int总规定是16位,long int总是32位,int由具体实现决定,所以移植性导向的软件(嵌入式或者通信系统)较少用int类型(他们之间的关系为short<=int& lt;=long);
++和—作为自增和自减操作:
他们实现了汇编语言类型的处理,以不可中断的高优先级进行加1或者减1,此种方式产生的目标代码具有更高的效率:(浮点数也可以使用这两种操作);
后缀表达所得到的返回值是一个数值(这一性质决定了其不可作为=的左值),并且仅有他们可以在不显式使用=的情况中改变操作数本身的值;
前缀表达式得到的返回值的是一个变量,所以可以获取地址,作为左值。对于表达式num=5;total=num+ (++num);而言,最终值是12,表示优先处理+的两个加数,然后仅当进行+操作的时候才把num的值从内存取出(之前都是符号表示);
二者的区别是:
前缀式先将操作数增1(或减1),然后将变量本身返回并参与表达式的运算。后缀是先返回变量的原始值参与表达式的运算,然后再在变量本身做加1(或减1)运算;I++返回的是值,不能用于左值,++I返回的是I变量本身,所以可以作为左值;
lvalue 指那些单一表达式结束之后依然存在的持久对象。例如: obj,*ptr, prt[index], ++x 都是lvalue。rvalue 是指那些表达式结束时(在分号处)就不复存在的临时对象。例如:1729 , x + y , std::string("meow") , 和 x++ 都是 rvalue;
int i=0; cout<<i<<" "<<i++<<endl;上述语句中,每一个<<符号表示一次独立的函数调用,如果存在多个函数调用,那么参数值 会从右到左处理一遍,之后再从左到右调用每个函数。从右到左处理参数是为了满足 C/C++ 的变参数函数的要求,而从左到右的调用函数则是与书写习惯相符合的,所以打印的结果为1,0。那么,这个语句中,先把 i 的值赋给第二个输出流操作符,然后进行自加,再将 i 的值(自加之后的)赋给第一个输出流操作符;printf("%d ,%d ", i , i++);由于是一次函数调用,所以打印结果都为0;
对于C++中用户定义类类型,使用四种方法可以将一种类型转换成另一种类型:
class B : public A { ……}
B公有继承自A,并且可是间接继承;这样B类对象可以转化成A类对象;
class B { operator A( ); }
B实现了隐式转化为A的转化,转换函数,不能有返回值和参数,并且为非静态,非有元函数;这样当遇到语句A=B的时候,B就调用poerator A()方法转换成A类型;
class A { A( const B& ); }
A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数;这样当初始化A类对象的时候,A a=B;就可以调用此构造函数;
A& operator= ( const A& );
赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个;这样当遇到语句B=A的时候,B就调用重载赋值操作符转换成A;
两类特性决定C++中程序变量的特性:
c++中变量的六种词法作用域(Lexical Scope),为程序变量的正确使用和操作,以及方便阅读,词法作用域描述变量的编译阶段特性(满足程序文法要求);并且变量或者函数的名字在同一作用域中要求唯一性(嵌套除外):块,函数,文件,程序,类,名字空间;
C++中变量的三种存储类别(Storage Type):变量名和其在内存中对应单元的关联属于合法时(内存有效分配)的一段有效的运行时间段内,决定变量的物理存储空间。词法作用域描述变量的编译 阶段特性,存储类别描述变量的运行阶段特性:静态固定内存,程序栈内存,程序堆动态内存;
C++程序的变量定义中,三种可以确定存储访问类别及其他特性的关键字:
- auto:块或函数作用域中定义为局部变量的自动变量(缺省),存储于程序栈内存;
好处是最小化了变量的作用域(名字唯一性冲突),最小化了内存的时间占用率(内存空间利用率);坏处是重复进行内存分配、释放影响性能,并且其大小在编译阶段就需要确定;(如果使用动态内存的话,就相当于使用其管理的复杂性来换取数据定义的灵活性);
编译器不会帮助进行初始化,仅当程序执行到某一个作用域时,该作用域中的auto变量才存在;
函数的形式参数也是该函数作用域中的auto变量,利用在函数调用中的实际参数的值进行初始化;
- extern:定义在函数或者块之外的变量,目的是为访问其他文件作用域的变量或函数,而定义的全局变量,存储于固定内存;
全局变量(全局变量缺省为外部变量)在main函数之前进行内存分配,并进行初始化(若没有主动初始化,则系统缺省初始化为0),在main函数执行完全之后才释放;
优点是一次进行内存计算和分配,提升程序执行速度;全程序空间和时间都可以使用,减少函数参数个数,减少栈空间耗用;坏处是内存占用率高,代码可读性差,可修改性差;
为了访问另一个文件中的数组,使用extern double cout[];但是不能写明元素个数,因为此处仅是引用申明,不是定义;extern本身有两层意义:一是当前文件中定义的变量可以在其他文件被访问(缺 省extern也可以);二是当前文件中申明的变量是引用其他文件定义的变量;
extern int count=0; 或者int count; 表示count为当前文件定义的变量;
extern int count; 表示count为引用其他文件的一个变量(如果其他文件也没有定义,则程序连接阶段将出现缺少定义的error); - static:与作用域无关(全局函数或者全局变量除外),仅影响变量的存储域,存储于固定内存中;
被static修饰的变量,在main函数执行之前分配内存,全局变量分配空间的时候进行初始化;可以为文件作用域的全局变量,也可以为块或者函数作用域中的局部变量;
有三种上下文可以使用static修饰(设置作用域,设置存储域);
修饰【全局变量,全局函数】的时候表明仅同文件内部可见,其他文件不能通过extern访问,并且不改变其存储位置和生命周期;另外一个好处是其他文件可以使用相同的名字定义变量和函数,也就是全局唯一性消失;
修饰【语句块,函数内的变量】的时候表明此变量可重复使用,也就是独立于语句块或者函数的作用域存在,存储空间为固定内存,生命周期为全程序(main函 数调用之前就已经分配内存并初始化,初始化为'0'),但引用域仍为该语句块,也就是仅当程序执行到其所在作用域时,该变量才可见;
修饰【类定义中的变量,函数】,表明此成员为类级别,独立于实例对象(对static函数而言,不再能访问非静态类域或者非静态函数);常用于针对某种类型的所有对象的信息簿记,或者工具类或者变量;
- register:cache中的变量,高速缓存中;用于存储那些会多次被使用到的变量,一般由编译器自行决定哪些变量应该作为register类型,程序员只能给出建议;
函数调用中,若实际参数跟形式参数的类型不相同,或者赋值操作中,有三种处理方法:
一种是如果可以通过类型提升和隐式类型转换将实参类型转换为形参类型(类型不同,但需要具有相同的操作集合,也就是相容类型),则程序可以正确编译,首先执行的是位宽提升,如果不能达到目的,才使用隐式类型转换:
如果是较短的位宽转换为较长的位宽,无信息损失,则正确执行;
如果是较长位宽的类型转换为较短位宽类型之后,则会造成类型精度的丢失,但程序还是继续执行;
对于相容类型而言,程序员可以使用显示的类型转换操作'(int)x'进行转换操作;显示的转换操作可以使得程序可读性更强;指针类型不支持隐式的转换 (除非父子继承类型),可以通过显示的转换(int*)&x;但是如果x的类型不是int,则编译器会按照int位模式进行解释,即使x的物理位 模式是double;或者使用程序员自定义的opeerator重载函数;
对于非相容类型而言,如果形参和实参的类型不能相互转换(如内置类型int和程序员定义类型Node,他们具有完全不同的操作集合,则不能实现转换),此时编译程序出现错误;当然除非有程序员自定义的类型转换函数,或者转换构造函数;
const修饰变量的意图:
使用const修饰一般数值变量,则这个变量不能进行赋值操作,甚至不能作为指针和引用变量赋值操作的右值(除非指针或者引用变量也为常量),目的都是为了防止任何直接或者间接地改变(const int a与int const a相同);
使用const修饰指针,引用类型(const int *a或者int const *a;引用的声明形式类似),如const int *ptrtemp; const int &reftemp;表示由他们指向的内存内容不能有任何直接或者间接地改变,除去索引之后和一般索引变量的限制相同;
使用const修饰指针变量本身,如int * const ptrtemp;表示指针变量本身不能再指向其他地址,也就是只能在变量初始化时进行赋值,这一性质和索引变量相同,索引变量可以看做指针变量本身const修饰,同时指针变量指向的内存内容可以改变:
const int val=10;
int *p=&val;//错误
int &r=val;//错误
const int *constP=&val;//正确
const int &constR=val;//正确
Account* largerOne(const Account &a1, const Account &a2) {
return (a1,value > a2.value) ? &a1:&a2;}//错误
由于传入参数为const,如果试图将其作为返回值,则返回值类型需要设置为const
同时const还可以传递程序员的意图:比如const的函数参数是作为输入值,非const的函数参数是作为修改值或者输出值,const的函数返回值 要求同样是const的右值变量,const的类成员函数表示const *this的缺省参数,也就是不能修改任何实例的类变量;
类成员函数的重载、覆盖和隐藏之间的区别:
- 重载(同一个类的成员函数之间的动作,基本语法):
1) 相同范围,同一个类
2) 函数名字相同
3) 参数(顺序,个数,类型)不同
4) virtual等函数修饰符不能有效区分重载函数
5) 重载函数不能有效区分:const和非const的参数类型;
6) 重载函数不能有效区分:索引类型与一般类型的参数
7) 函数返回值不能有效区分重载函数; - 覆盖(派生类和基类的成员函数之间的动作,多态机制):
1) 不同范围,派生类和基类
2) 函数名字相同
3) 参数(顺序,个数,类型)完全相同
4) 位于基类的函数必须有virtual关键字 - 隐藏(派生类的函数屏蔽了与其同名的基类的函数,基本语法):
1)不同范围,派生类和基类
2) 函数名字相同
3) 如果只是函数名字相同,参数不同,则无论是否有virtual关键字,派生类的函数都将屏蔽基类的函数
4) 如果函数名字和参数都完全相同,并且基类函数没有virtual关键字(有的话就是覆盖),则派生类的函数都将屏蔽基类的函数
高质量程序设计的几个要点:
将职责从客户端推向服务器端;
限制客户端和服务器端的信息共享:
数据封装(Encapsulation):客户通过标准接口访问数据;
信息隐藏(Hide):客户不关心服务器的内部情况也可使用服务;
避免将应该在一起的代码分开,在代码块间增加无谓的通信流(传入参数和返回值),增加模块之间的依赖性:
低耦合性(Coupling)是模块间的关系,避免应该分开的代码合在一起;隐式耦合度表示多个函数使用全局变量的个数(涉及到初始化的问题,最强的耦合度),显式耦合度表示函数调用之间传入和输出的参数的个数;不适当的耦合度都是因为程序功能不恰当分配造成的;
高内聚性(cohesive)是模块内部逻辑间的关系,避免不相关的操作合在一起;
使用代码本身而不是注释阐述程序意图,反过来注释不应该只是解释程序本身的逻辑,而是阐述程序的意图和注意事项;
模块设计的时候考虑可维护性(Maintainability)和可重用性(Reusability)
关于C/C++的一些思考(1)的更多相关文章
- 领域驱动和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(或者叫实体类).有 ...
随机推荐
- 【140】◀▶ ArcGIS技巧
目录: Add XY Data 图例修改 中文字符左斜体设置 专题图只显示“度” 制作渐变图例 待定 待定 待定 1. 在ArcGIS中插入含有经纬度的*.txt或者*.xls文件等 File> ...
- C#托盘图标
在C#中实现托盘是多么简单 http://www.cnblogs.com/anytao/archive/2006/04/26/385377.html http://www.cnblogs.com/du ...
- Ceph之PG数调整
1. PG介绍 PG, Placement Groups.CRUSH先将数据分解成一组对象,然后根据对象名称.复制级别和系统中的PG数等信息执行散列操作,再将结果生成PG ID.可以将PG看做一个逻辑 ...
- [POI2008]海报PLA
Description N个矩形,排成一排. 现在希望用尽量少的矩形海报Cover住它们. Input 第一行给出数字N,代表有N个矩形.N在[1,250000] 下面N行,每行给出矩形的长与宽.其值 ...
- C plus plus sprintf用法
sprintf int sprintf ( char * str, const char * format, ... ); Write formatted data to string Compose ...
- Exception in thread "main" java.lang.NoClassDefFoundError: org/jaxen/NamespaceContext
使用dom4j的xpath查询节点,报如下错误: Exception in thread "main" java.lang.NoClassDefFoundError: org/ja ...
- GOTO语句以及GOTO机制的模式实现
goto语句提供了方法内部的任意跳转,它在特殊场景下被应用. 而假设一个对象执行一个方法后,我们期望其余任何对象都可以捕获它,然后自己执行某些操作,那么可以怎么实现呢 class 皇宫 { void ...
- java数组实现买彩票(二个一维数组的比较思想)
/** 设计一个程序,模拟从彩球池里随机抽取5个彩球(彩球池里一共有11个彩球,编号为1~11), 要求在控制台打印出这5个被取出来的彩球的编号(注意编号不能重复). 思路: 1.创建一个int类型的 ...
- 转 oracle apex 使用
https://wenku.baidu.com/view/e5a4226955270722182ef725.html
- 446 Arithmetic Slices II - Subsequence 算数切片之二 - 子序列
详见:https://leetcode.com/problems/arithmetic-slices-ii-subsequence/description/ C++: class Solution { ...