C/C++程序设计2——基本概念2

目录

  • 基本数据类型
  • 类型转换
    • 隐式转换
    • 强制转换
  • 标识符
  • 转义序列
  • 运算符
  • 表达式

基本数据类型

       每种数据类型都有对应的特定字节数,比如32位系统中int类型就是4个字节,double型就是8个字节。字节就是内存编址的最小单位,C和C++的各种数据类型的变量都至少会占用1字节的内存。具体占多少,可以使用sizeof()来确定。
       在C中,基本的数据类型包括:int,long,float,char,double,void,以及它们和signed、unsigned、*、&的组合。到了C++,基本类型类增加了bool类型,同时也增加了两个符号常量TRUE和FALSE。这两个是C++的关键字。
       特别要提一下,void类型的含义是“空”类型。这种类型的大小是无法确定的。很显然,也不存在void类型的变量或对象,sizeof()也不能用在void上。因为C或C++不能对大小未知的对象进行直接操作。void类型一般是用来定义函数的返回类型、参数列表(无参)和void指针。
       void指针是一个通用指针,可以指向任何类型的对象。
       这里要注意一点,要区分void类型指针和NULL指针之间的区别。NULL是可以付给任何类型指针的值。对于NULL来讲,C和C++还有些许不同。在C语言中,NULL的类型为void*,但在C++中,由于允许从0到任何指针类型的隐式转换,NULL就是整数0。

#ifdef __cplusplus
#define NULL 	0
#else
#define NULL 	((void *)0)
#endif

       void*类型的指针一般在函数参数中传递函数与调用者之间约定好类型的对象地址。
       如果指针的值等于NULL,那这个指针是一个合法的指针,但却不是有效的指针。
       bool类型的变量只有两个,TRUE和FALSE。按理来说,只需要一个bit就可以表示。但是前面说了,字节是内存编址的最小单位,所以bool类型的变量占内存的大小也是1字节。看上去浪费了7bit。
       标准C语言中是没有bool类型的,但某些C的库里做了映射,像这样:

typedef int BOOL;
#define TRUE 	1
#define FALSE 	0

       所以包含了对应的库也就定义了bool类型。
       在标准C语言里,int被称为默认类型,前一文章中提到的main()就是int类型。事实上,如果你在写函数的时候不明确的定义形参类型或者返回值类型,那它们就默认为int类型。然而,在C++中却是不支持默认类型的,所以不要搞混。我的建议是,不要使用默认数据类型。要明确的指出函数每个形参的类型和返回值类型。

类型转换

       这是C和C++学习过程中绕不过去的知识,或者说是所有编程语言都绕不过去的东西。
       从本质上说,C和C++是不会对两个类型不同的操作数进行运算的。如果类型不同,编译器就会试图进行类型转换。不管是隐式的,还是用户要求的强制的。类型转换并不是改变原来操作数的类型和值,而是生成一个新的临时变元。

隐式转换

       所谓隐式转换,是编译器在背后自动进行的转换工作,写程序的人不不会感觉到。那么隐式转换是否是安全的,就是各种编译器必须考虑的问题。如果编译器认为类型转换有安全隐患,就会报错提醒。当然,如果写程序的人确实想这么做,就需要显式的进行强制转换。
       为什么会有类型转换的安全性呢?。因为不同的数据类型所占的内存字节数是不一样的,那么在转换时就可能会发生内存扩张、内存截断、尾数截断、值的改变以及数据溢出等情况。
       因此在编译器隐式转换遵循一个基本的原则,字节数低的类型转换为高的类型基本安全。比如,char可以转换为int,int可以转换为long,long可以转换为float,float可以转换为double。
       另外,转换时总是优先转换为能够容纳得下它的最大值的、占用内存最少的类型。比如看下面两个重载函数:

void f(long l);
void f(double d);

       那么在调用f(100)时,必然会调用f(long l)而不是f(double d)。

       上面说的是基本类型的转换,那么派生类和基类的转换又是什么样的呢?在C++中是可以直接将派生类对象转换为基类对象的。当然,这样转换后会发生内存截断,在内存访问和转换结果方面来说都是安全的。这是因为C++有一条保证:派生类对象必须保证其基类子对象的完整性。所以下面这段代码是安全的。

class Base
{
private:
	int m_a;
	int m_b;
};
class Derived : Base
{
	int m_c;
};
Derived objD1;
Base objB1 = objD1;

Derived *pD1 = &objD1;
Base *pB1 = pD1;

       在C语言中,允许任何非void类型指针和void类型指针之间进行直接的相互转换。比如可以先把int类型转换为void类型,在把void类型转换为double类型。这在C中是允许的,也不会报错,但是这么做可能会产生不易察觉的安全问题,比如内存扩张和截断什么的。说实在的,这其实是C语言的一个缺陷。在C++中就不是这样了,C++中只可以把任何类型的指针直接指派给void指针,却不能反过来把void指针直接指派给任何非void指针,除非进行强制转换。

强制转换

       使用强制转换就一定会有安全风险,导致一些不希望看到的结果。比如下面这个例子:

double d3 = 1.25e+20;
double d4 = 10.25;
int i2 = (int)d3;
int i3 = (int)d4;

       从浮点数转换为整数的过程是去掉浮点数的小数部分。因此i3会得到10,但是i2呢,它会溢出。d3的值整数部分远远超出了int类型所能表示的最大值了。
       指针之间的强制转换问题更多,比如int、long、float都是4字节的类型,强制转换虽然不会早证内存扩张,但如果它们之间的指针转换改变了编译器对指针指向的内存单元的解释方式,结果必然是错误的。比如:

double d5 = 1000.25;
int *pInt = (int*)&d5;
int i4 = 100;
double *pDbl = (double*)&i4;

       用pInt访问d5不会报错的,但是它的值可绝对不会是1000,它是d5开头四个字节的内容被解释为int类型的值。同样,pDbl访问i4获得的值也不一定是100,因为内存被扩张了。
       同样,在基类和派生类之间进行强制转换也有安全隐患。比如:

Base objB2;
Derived *pD2 = (Derived *)&objB2;

       pD2能够方位的内存范围要比objB2所占用的内存要大,也就是内存扩张了。所以,这时访问pD2的m_c就有可能产生运行错误。因为pD2指向的对象里根本就没有m_c的空间。
       所以在写程序的时候有这么几条还是要注意的。

  • 不可以把基类对象直接转换为派生类对象,无论是直接赋值还是强制转换。
  • 对于基本类型的强制转换一定要区分值的截断和内存截断是不同的概念;
  • 如果坚持要使用强制转换,必须保证内存访问的安全性和转换结果的安全性;
  • 如果确信需要类型转换,尽量使用显式的强制类型转换,让看你程序的人知道发生了什么事,同时也可以避免编译器隐式转换可能会带来的你不希望的结果。

       还要提醒一下,尽量避免做违反编译器类型安全原则和数据保护原则的事情,比如在无符号和有符号数之间转换,或者把const对象的地址指派给非const对象指针,等等。

标识符

       C++和C中的标识符是由字母、数字和下划线组成的字符序列,用来标识一个程序元素,比如变量、函数、宏、类型名等。在标准C语言里,标识符的有效长度不超过31位,超过31的编译器会不认。而在C++里,这个长度是255位。
       每个标识符都有几个具体的属性:值、值类型、名字、存储类型、作用域范围、连接类型、生存期等等。比如,函数名就是一个标识符它的值就是函数体在内存中的首地址,编译时就确定,是一个常量;值的类型就是函数指针类型;存储类型默认为extern,除非把函数声明为static;作用域为文件作用域;连接类型默认为外连接,生存期为永久。
       另外,请避免使用前导“_”和"__"来定义标识符,因为C语言及其实现使用这个东西定义一些内部名称或预定义的宏,容易让人误会,而如果造成命名冲突那问题就更严重了。
       给标识符起名字尽量起一个有意义的名字。如果是变量,最好在变量的名字里能体现出它的值得类型。
       长的标识符并不会增加可执行代码的体积,因此不要使用过于简单的名字。但也没必要使用过长的名字,用最短的名字包含最多的信息量。

转义序列

       在C和C++中,有些字符是具有特殊含义的,比如%,“”,?,\等等。但有些时候,我们希望把他们当普通字符处理,比如把它们输出到控制台上。这时就要做一些特殊处理了。一般来说有两种方法:一种是直接引用ASCII码,另一种就是使用转义序列了。转义序列是由反斜杠()跟后面一个特定转义字符组成的。我把常见的转义序列做个表,供大家查阅。

转义序列ASCII说明
\’0x27输出单引号字符本身
\”0x22输出双引号字符 本身
\?0x3F输出问号字符本身
\\0x5C输出反斜杠字符本身
\a0x07触发扬声器发出蜂鸣声
\b0x08使光标退一格
\f0x0C输出换页
\n0x0A换行
\r0x0D回车符
\t0x09水平制表符
\v0x0B垂直制表符
\00x00空字符

       “换行”和“回车”?,这两个不一样吗。额,有点区别,首先,这两个的ASCII码不一样。“换行”符一般用于文件操作,比如把键盘输入的回车操作转换为“换行”字符来保存,而不是回车。“换行”用于输出控制,指示终端输出从新行开始,而“回车”是键盘功能,用于输入控制。因此,请记住:输出“换行”,输入“回车”。不过C语言中有些字符输入函数可以把键盘输入的“回车”字符自动转换为“换行”字符返回。比如getchar()。
       另外,由%引导的字符序列,比如%d,%f,%%等,也是转义序列。

运算符

       C和C++的运算符大致可以分为三种:算术运算符、关系运算符和逻辑运算符。还有一些其他的,比如函数调用、类型转换、成员选择,以及C++提供的类型识别(typeid),作用域解析(::)、动态内存分配和释放(new\delete)、类成员指针这些也叫运算符。
       各种运算符和操作数就会构成表达式,那么计算机执行表达式的运算符时就会遵守优先级规则运行。不同的运算符优先级不同,优先级高的先计算,低的后计算。
       几乎所有C语言或者C++语言参考书里都有运算符优先级的排列。其实我觉得,没必要死记硬背,记住几个最关键的先括号内,再括号外,先乘除后加减,先算术后逻辑、先计算后赋值,其他现查也来得及。实在把握不了,用小括号来组织是非常良好的习惯,容易理解且不易产生歧义。
       “? :”是C和C++中唯一的一个三元运算符,语法是:
                     条件表达式?表达式1:表达式2
       意思是,如果条件表达式为true,则整个表达式的值就是表达式1的值,表达式2忽略;否则就是表达式2的值,表达式1忽略。有点像if/else的结构。
       稍微提一下++和–运算符。它们的前置版本和后置版本在单独使用时,效果时一样的。但是当它们在复杂表达式中时,前置版本和后置版本才具有不同效果。这个以后谈到“运算符重载”的时候,我在详细讨论吧。
       再有,一定要注意一些不易分辨的运算符,比如“==”和“=”,“&&”和“&”。有过写程序做条件判断时,把“==”误写成“=”的同学请举手。编译器可不一定会自动指出这类的错误。

表达式

       通俗点讲,表达式就是使用运算符和标识符按照语法规则连接起来的算式。任何表达式都是有值的。常见的表达式包括常量表达式、算术表达式、关系表达式、逻辑表达式以及复合表达式。此外还有些不常用的,比如逗号表达式、条件运算符(? :),位运算表达式等等。
       注意,不要把数学中的表达式和计算机语言中的表达式混淆。比如:
                            if(a<b<c)
       并不表示
                            if((a<b)&&(b<c))
       而是表示令人费解的
                            if((a<b)<c)
       常量表达式全部由常量(字面常量、符号常量、枚举常量、布尔常量等)和运算符组成。由于常量在运行时不能改变,所以常量表达式没有必要在运行时在计算,而是编译时就求值了。
       那么,在编译时求值的程序元素是否需要分配运行时的存储空间呢?这就要看它是什么类型的程序元素了。比如基本类型的字面常量、枚举常量、sizeof()、常量表达式等就不需要分配存储空间,因此也没有存储类型,但是字符串常量、const常量都要分配运行时的存储空间,即有特定的存储类型。
       不要使用算术表达式作为单独的语句,一般算术表达式都和赋值运算符(=)一起使用,否则没有保存计算结果却白白消耗时间去计算它实在没必要。
       关系表达式,也就是由大于号、小于号这些东西构成的表达式总是返回true或false。
       逻辑表达式同样也总是返回true或false。建议,在使用逻辑运算符"&&“时,尽可能把最有可能为false的子表达式放在“&&”的左边;同样,在使用“||”运算符时,尽可能把最有可能为true的子表达式放在左边。这是因为C和C++对逻辑表达式的判断采取“突然死亡法”。如果”&&"左边的计算结果为false,则整个表达式就是false,后边的子表达式没必要计算。同理,“||”左边的表达式计算结果为true,则整个表达式就是true,后面的也不计算了。这种方法可以提高程序的执行效率。
如果把简单表达式,通过算术的、关系的和逻辑的运算组合成一个表达式,就是复合表达式。其实,我不建议使用复合表达式,因为它很有可能存在隐患。想来想去,使用复合表达式的理由恐怕也就下面两条:

  • 书写简洁,使用简单表达式要完成相同功能需要更多周折
  • 生成个的可执行代码更加高效。

       但即使这样,我这里仍建议要防止滥用复合表达式,而且,不要编写太过复杂的复合表达式。比如下面这个表达式:
              i = a >= b && c < d && c + f <= g + h;
       是不是看得晕头转向的。
       再有,不要编写多用途的复合表达式。比如:
              d = (a = b + c) + r;
       上面这个表达式应该拆分成:
              a = b + c;
              d = a + r;

热门文章

暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法&#xff0c;在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较&#xff0c;从而确定目标数是在中间数的左边还是右边&#xff0c;将查…
暂无图片
编程学习 ·

GMX 命令分类列表

建模和计算操作命令&#xff1a; 1.1 . 创建拓扑与坐标文件 gmx editconf - 编辑模拟盒子以及写入子组(subgroups) gmx protonate - 结构质子化 gmx x2top - 根据坐标生成原始拓扑文件 gmx solvate - 体系溶剂化 gmx insert-molecules - 将分子插入已有空位 gmx genconf - 增加…
暂无图片
编程学习 ·

一文高效回顾研究生课程《数值分析》重点

数值分析这门课的本质就是用离散的已知点去估计整体&#xff0c;就是由黑盒子产生的结果去估计这个黑盒子。在数学里这个黑盒子就是一个函数嘛&#xff0c;这门课会介绍许多方法去利用离散点最大化地逼近这个函数&#xff0c;甚至它的导数、积分&#xff0c;甚至微分方程的解。…
暂无图片
编程学习 ·

在职阿里5年,一个28岁女软测工程师的心声

简单的先说一下&#xff0c;坐标杭州&#xff0c;14届本科毕业&#xff0c;算上年前在阿里巴巴的面试&#xff0c;一共有面试了有6家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; ​ 编辑切换为居中…
暂无图片
编程学习 ·

字符串左旋c语言

目录 题目&#xff1a; 解题思路&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 总代码&#xff1a; 题目&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符…
暂无图片
编程学习 ·

设计模式--观察者模式笔记

模式的定义与特点 观察者&#xff08;Observer&#xff09;模式的定义&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&#xf…
暂无图片
编程学习 ·

睡觉突然身体动不了,什么是睡眠痽痪症

很多朋友可能有这样的体验&#xff0c;睡觉过程中突然意识清醒&#xff0c;身体却动弹不了。这时候感觉非常恐怖&#xff0c;希望旁边有一个人推自己一下。阳光以前也经常会碰到这样的情况&#xff0c;一年有一百多次&#xff0c;那时候很害怕晚上到来&#xff0c;睡觉了就会出…
暂无图片
编程学习 ·

深入理解C++智能指针——浅析MSVC源码

文章目录unique_ptrshared_ptr 与 weak_ptrstd::bad_weak_ptr 异常std::enable_shared_from_thisunique_ptr unique_ptr 是一个只移型别&#xff08;move-only type&#xff0c;只移型别还有std::mutex等&#xff09;。 结合一下工厂模式&#xff0c;看看其基本用法&#xff…
暂无图片
编程学习 ·

@TableField(exist = false)

TableField(exist false) //申明此字段不在数据库存在&#xff0c;但代码中需要用到它&#xff0c;通知Mybatis-plus在做写库操作是忽略它。,.
暂无图片
编程学习 ·

Java Web day15

第十二章文件上传和下载 一、如何实现文件上传 要实现Web开发中的文件上传功能&#xff0c;通常需要完成两步操作&#xff1a;一.是在Web页面中添加上传输入项&#xff1b;二是在Servlet中读取上传文件的数据&#xff0c;并保存到本地硬盘中。 需要使用一个Apache组织提供一个…
暂无图片
编程学习 ·

【51nod 2478】【单调栈】【前缀和】小b接水

小b接水题目解题思路Code51nod 2478 小b接水 题目 输入样例 12 0 1 0 2 1 0 1 3 2 1 2 1输出样例 6解题思路 可以发现最后能拦住水的都是向两边递减高度&#xff08;&#xff1f;&#xff09; 不管两个高积木之间的的积木是怎样乱七八糟的高度&#xff0c;最后能用来装水的…
暂无图片
编程学习 ·

花了大半天写了一个UVC扩展单元调试工具

基于DIRECTSHOW 实现的&#xff0c;用的是MFC VS2019. 详见&#xff1a;http://www.usbzh.com/article/detail-761.html 获取方法 加QQ群:952873936&#xff0c;然后在群文件\USB调试工具&测试软件\UVCXU-V1.0(UVC扩展单元调试工具-USB中文网官方版).exe USB中文网 USB中文…
暂无图片
编程学习 ·

贪心(一):区间问题、Huffman树

区间问题 例题一&#xff1a;区间选点 给定 N 个闭区间 [ai,bi]请你在数轴上选择尽量少的点&#xff0c;使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数 N&#xff0c;表示区间数。 接下来 …
暂无图片
编程学习 ·

C语言练习实例——费氏数列

目录 题目 解法 输出结果 题目 Fibonacci为1200年代的欧洲数学家&#xff0c;在他的着作中曾经提到&#xff1a;「若有一只免子每个月生一只小免子&#xff0c;一个月后小免子也开始生产。起初只有一只免子&#xff0c;一个月后就有两只免子&#xff0c;二个月后有三只免子…
暂无图片
编程学习 ·

Android开发(2): Android 资源

个人笔记整理 Android 资源 Android中的资源&#xff0c;一般分为两类&#xff1a; 系统内置资源&#xff1a;Android SDK中所提供的已经定义好的资源&#xff0c;用户可以直接拿来使用。 用户自定义资源&#xff1a;用户自己定义或引入的&#xff0c;只适用于当前应用的资源…
暂无图片
编程学习 ·

零基础如何在短时间内拿到算法offer

​算法工程师是利用算法处理事物的职业 算法&#xff08;Algorithm&#xff09;是一系列解决问题的清晰指令&#xff0c;也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输出。 如果一个算法有缺陷&#xff0c;或不适合于某个问题&#xff0c;执…
暂无图片
编程学习 ·

人工智能:知识图谱实战总结

人工智能python&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习人工智能&#xff1a;知识图谱实战前言一、实体建模工具Protegepython&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习 人工智能&#xff1a;知识图…
暂无图片
编程学习 ·

【无标题】

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…