几个小问题:

(1)c++语言是什么时候检查数据类型的?(这么提问好像不合适,但是又不知道怎么表达,先这样吧)

一些语言,如smalltalk和python等,是在程序运行的时候检查数据类型的;与之相反,c++是一种静态数据类型语言,它的类型检查发生在编译时。

(2)i=i+j; 的含义?(一个小坑)

这个问题其实就是考察对数据类型的理解。c++中(不仅仅是c++),数据类型是程序的基础,它告诉我们数据的意义以及我们能够在数据上执行的操作。在这个问题中,如果i、j都是整型数据,那么这条语句执行的就是最简单的加法操作,如果是字符串,就是执行的字符串拼接操作。

一、基本内置类型

c++的基本数据类型可以分为两类:算术类型、空类型。

1、空类型

      空类型不对应具体的数值,仅用在一些特殊的场合当中,比如当函数不返回任何值时就使用空类型作为函数的返回值类型

2、算术类型

      算术类型分为两类:整型和浮点型。

      整型分为三种:布尔型、字符型、整型数。

  1. 布尔类型的取值为true或者false
  2. 字符型被分为了三种:char、unsigned char、signed char
    1. 尽管字符型有三种,但是字符的表现形式只有两种:带符号的和无符号的。类型char实际上会表现为上述两种表现形式中的一种,具体是哪儿一种由编译器决定。
    2. 无符号类型中所有比特都用来存储值,例如:8比特的unsigned char可以表示0-255区间内的值。c++标准并没有规定有符号类型应该如何表示,但是约定了在表示范围内正值和负值的量应该平衡。因此,8比特的signed char理论上应该可以表示-127到127区间内的值,大多数计算机将实际的表示范围定为-128 至 127。
  3. 整型数也可以分为以下几种类型:short、int、long、long long。用于表示不同尺寸的整数。其中long long类型是在c++ 11标准中新定义的。
  4. 浮点型可以表示单精度浮点型(float)、双精度浮点型(double)、扩展精度浮点型(long double)

3、如何选择数据类型的建议?

  1. 当明确知晓数值不可能为负值时,选择使用无符号类型。
  2. 使用int来进行整数运算。如果数值超过了int的表示范围,则选择使用long long类型。
  3. 在算术表达式中不要使用char或者bool,只有在存放字符或者布尔值时才使用它们。因为类型char在一些机器上是有符号的,在另外一些机器上又是无符号的,所以如果使用char进行运算特别容易出问题。如果需要使用一个不大的整数,请明确指定它的类型unsigned char或signed char。
  4. 执行浮点数运算选用double。因此float通常会精度不够,而且双精度浮点数和单精度浮点数的计算代价相差无几。事实上,对于某些机器来说,双精度运算甚至比单精度还快。long double提供的精度在一般情况下是没有必要的,况且它带来的运算时消耗也不容忽视。

二、类型转换

对象的类型定义了对象能包含的数据以及能参与的运算,其中一种运算被大多数类型所支持,就是将对象从一种给定的类型转换为另一种相关类型。

当在程序的某处我们使用了一种类型,而其实对象应该取另一种类型时,程序会自动进行类型转换。下面我们来看一下在类型转换的时候,到底会发生什么:

三、字面值常量

整型字面值常量

我们可以将整型字面值常量写作十进制数、八进制数或者十六进制数的形式。以0开头的整数代表八进制数,以0x或0X开头的代表十六进制数。例如,我们可以用下面任意一种形式来表示数值20 : 20、024、0x14.

整型字面值具体的数据类型由它的值和符号来决定。在默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是不带符号的。十进制字面值的类型是int、long、long long中尺寸最小的那个,前提是这种类型要能容纳下当前的值。八进制和十六进制字面值的类型是能容纳其数值的int、unsigned int、long、unsigned long、long long和unsigned long long中的尺寸最小者。类型short没有对应的字面值。

尽管整型字面值可以存储在带符号数据类型中,但是严格来说,十进制字面值不会是负数。比如-42,负号并不在字面值中,它的作用仅仅是对字面值取负值而已。

浮点型字面值常量

浮点型字面值常量默认是double类型。

字符和字符串字面值

要注意的是编译器会在每个字符串的结尾处添加一个空字符\0,因此,字面值的实际长度比它的内容要多1.

转义序列

有两类字符程序员不能直接使用:

  1. 不可打印的字符,比如退格(或者其他控制字符),它们没有可视的图符
  2. 在c++语言中有特殊含义的字符(单引号、双引号、问号、反斜线)

四、变量

       变量提供一个具名的、可供程序操作的存储空间。

       C++中每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。

C++中extern关键字的用法

       C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件都可以被独立编译。为了将程序分为许多个文件,则需要在文件共享代码,例如一个文件的代码可能需要引用在另一个文件中定义的变量。

       为了支持分离式编译,C++允许将声明和定义分离开来。变量的声明规定了变量的类型和名字,即使得一个名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义则负责创建与名字关联的实体,定义还会申请存储空间。

       在C/C++中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。

用extern修饰变量的声明:

       举例来说,如果文件a.c中需要引用文件b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。要注意能够被其他模块以extern修饰符引用到的变量通常是全局变量。

       还要注意的一点就是,extern int v可以放在文件a.c中的任何地方,比如你可以在a.c中的函数fun()定义的开头处声明extern int v,然后在这个函数中就可以引用变量v了,只不过这样只能在函数fun()中引用。这说到底还是变量作用域的问题。注意extern声明并不是只能用于文件作用域。

用extern修饰函数的声明:

       从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。

       比如在文件b.c中有一个函数fun,其函数原型是int fun(int m),如果文件a.c需要引用这个函数,只需要在a.c中声明extern int fun(int m),然后就可以随意的使用fun()了。与变量相同,extern int fun(int m)也不一定要放在a.c的文件作用域的范围中。

使用extern声明外部定义和使用inlude命令包含有什么区别呢?

       如果对C/C++熟悉的朋友可能马上会想到,如果我们要使用另一个文件中定义的变量或者函数,只需要把这个变量或者函数定义在一个头文件中,然后在需要引用它的文件中使用include命令包含进来即可。这样当然是没有错的。

       那么这两种方式究竟有什么区别呢?难道没有发现extern的引用方式比包含头文件的方式要简洁的多吗。在任何时候,任何位置,只要你向引用另一个文件中的变量/函数,就可以用extern声明。当然最重要的好处是:使用extern会加速程序的编译(确切的说是预处理)的过程,节省时间,在大型C/C++程序编译过程中,这种差异是非常明显的。

一个extern使用的例子。

下面有一个场景:现在有两个文件a.c和b.c,他们都要使用到变量int common_data,如何来实现呢?(很简单的一个例子,不要当真哦)

(1)对于一个初学者来说(我在刚学的时候最先想到的也是这个方案),创建一个头文件common.h,把int common_data定义在这个头文件中,然后在a.c和b.c中用include命令将common.h包含进来。乍一看是非常合理的,但是如果尝试了会发现报下列错误:

c++ primer 第2章  变量和基本类型-LMLPHP

变量重定义错误。想一下为什么会出现这个错误呢?include命令就相当于把头文件中文件的内容copy到include的位置,这就相当a.c和b.c都执行了一次int common_data;在C/C++中全局变量对整个工程是可见的,所以这样就相当于在工程内定义了两个同名的变量,当然会报变量重定义错误了。

(2)用extern来实现

b.cpp中的内容:

#include <iostream>

int common_data = 666;

void print_common_data()
{
	std::cout << common_data << std::endl;
}

a.cpp中的内容:

#include <stdlib.h>

extern int common_data;
extern void print_common_data();

int main(int argc, char **argv)
{
	print_common_data();
	system("pause");
	return 0;
}

声明和定义的区别看起来也许微不足道,但是实际上非常重要。如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

变量命名规范

变量命名有许多约定俗成的规范,遵守这些规范有助于提高程序的可读性

  • 标识符要能够体现实际含义,如上面的common_data,表示它是一个公有变量
  • 变量名一般用小写字母
  • 用户自定义的类名一般用大写字母开头,如Sales_item
  • 如果标识符由多个单词组成,则单词间应该有明显的区分,如print_common_data或者printCommonDate,而不要使用printcommondata。

五、复合类型

复合类型是指基于其他类型定义的类型。C++中有好几种复合类型,这里先介绍其中两种:引用和指针。

引用

引用其实就是为对象起了一个别名,通过将声明符写成&xxx的形式来定义引用类型,其中xxx是声明的变量名。

如:

int value=1024;   

int &ref_value=value;      //ref_value指向value(ref_value是value的一个别名)

使用引用的时候要注意以下几点:

  1. 一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定在一起(我的理解是引用指向初始值所在的那一块存储空间),而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起(不能将一个已存在的引用重新定义为另一个变量的引用)。因为无法令引用重新绑定到另外一个对象,因此引用必须要进行初始化。如:int &ref_value;  //错误
  2. 定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的。
    1. 为引用赋值,实际上就是将值赋给了与引用绑定的对象。
    2. 获取引用的值,实际上是获取了与引用绑定的对象的值。
    3. 以引用作为初始值,实际上是以与引用绑定的对象作为初始值。
  3. 引用并不是一个对象,而是为一个已经存在的对象所起的另外一个名字。因为引用本身不是一个对象,所以不能定义引用的引用。而且,引用只能绑定在对象上,而不能与字面值常量或者某个表达式的计算结果绑定在一起。
    1. int &ref_value=10;   //错误,引用类型的初始值必须是一个对象
  4. 引用的类型要与绑定的对象严格匹配 。

下面看一个关于引用的例子:

#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    int a=10, b=20, c=0;
    cout<<"c = "<<c<<endl;
    int &a_1=a, &b_1=b, &c_1=c;
    c_1=a_1+b_1;
    cout<<"c = "<<c<<endl;
    return 0;
}

运行结果:

c++ primer 第2章  变量和基本类型-LMLPHP     

这是一个最简单的关于“引用”使用的例子。

指针

指针是“指向”另外一种类型对象的复合类型,与引用很相似,它也实现了对其他对象的间接访问。

那么指针和引用相比有什么区别呢?

  1. 指针本身就是一个对象,允许对指针进行赋值和拷贝,而且在指针的生命周期中它可以先后指向不同的对象。
  2. 指针无须在定义的时候赋初值。引用在定义时必须赋值,并且在之后不能试图让它成为其他对象的引用。

关于指针的一些操作:

  1. 指针中存放的是某个对象的地址,要获得某个对象的地址需要使用取地址符‘&’。
    1. 除了c++11中新定义的智能指针外,其他所有指针的类型都要和它所指向的对象类型严格匹配。
    2. 引用不是对象,没有实际地址,因此不能定义指向引用的指针。
  2. 如果指针指向了一个对象,如果要想访问该对象则需要使用解引用符‘*’。如*p(p是一个指针,指向对象ival)。对指针解引用会得到指针所指向的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值。、
  3. 空指针不指向任何对象,试图访问空指针都会产生错误,因此在试图使用一个指针之前应该首先检查它是否为空。获得空指针的方法如下(只讲述新标准下的方法,同时现在的程序建议使用本方法)。
    1. int *p=nullptr;   //p为空指针
    2. 说明:利用字面值常量nullptr来初始化指针,这是c++11新标准中刚引用的一种方法,nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。

下面来编写一个简单的示例程序。

场景:有两个数a=10,b=0,定义一个函数swap(),在其中实现将a、b的值交换。

用指针或者用引用都可以解决这个问题。将a、b作为swap()的参数传入。(如果有人要用全局变量来做当然也是可以的,但是这样就没意思了)。

#include <iostream>

using namespace std;

//用引用
void swap1(int &a, int &b)
{
    int temp=a;
    a=b;
    b=temp;
}
//用指针
void swap2(int *a, int *b)
{
    int temp=*a;
    *a=*b;
    *b=temp;
}

int main(int argc, char **argv)
{
    int a=10, b=0;
    cout<<"before swap: a="<<a<<" b="<<b<<endl;
    //swap1(a,b);  //用引用
    swap2(&a, &b);  //用指针
    cout<<"after swap: a="<<a<<" b="<<b<<endl;
    return 0;
}

另外还要注意的是:对于两个类型相同的合法指针,可以用 == 或者 != 来比较它们,比较的结果是布尔类型。

如果两个指针中存放的地址值相同,则它们相等;反之不相等。

两个指针存放的地址值相同有三种可能:

       1. 它们都为空

       2. 它们指向同一个对象

       3. 它们都指向了同一个对象的下一地址。

下面用一个示例程序来感受一下:​​​​​​​

#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
	int *ptr_1 = nullptr, *ptr_2 = nullptr;
	int value1 = 666, value2=999;

	cout << "两个指针都为空的时候:" << endl;
	cout << (ptr_1 == ptr_2) << endl;

	cout << "两个指针指向不同对象的时候:" << endl;
	ptr_1 = &value1;
	ptr_2 = &value2;
	cout << (ptr_1 == ptr_2) << endl;

	cout << "两个指针指向同一个对象的时候:" << endl;
	ptr_1 = &value1;
	ptr_2 = &value1;
	cout << (ptr_1 == ptr_2) << endl;

	cout << "两个指针指向同一个对象的下一地址的时候:" << endl;
	ptr_1++;
	ptr_2++;
	cout << (ptr_1 == ptr_2) << endl;

	return 0;
}

这些程序都是写着玩的,各位大佬不要笑话。哈哈哈...

10-06 22:03