C++学习笔记2:变量和基本类型

作者: 云中布衣   分类:  学习笔记    热度: (423℃)   时间: 2017-6-18 11:03   标签: #始于C++而不止于C++    

类型是所有程序的基础。类型能够告诉我们数据代表什么意思以及可以对数据执行那些操作。C++语言定义了集中基本的类型:字符型、整型、浮点型等。C++还提供了可用于自定义数据类型的机制,它的标准库正式利用这些机制定义了许多更加复杂的类型,比如变长字符串string、vector等。此外我们还可以通过修改已有的类型形成复合类型。

类型确定了数据和操作在程序中的意义!下面举个简单的例子,如下的语句:

i = i + j; 

该语句所代表的具体意义,取决于i和j的类型。如果i和j都是整型,则这条语句表示一般的算术“+”运算;如果i和j都是前面的Sales_item类型,则这条语句是将这两个对象的组成成分分别加起来。当然这个的前提是你需要在定义Sales_item类型时,在类里面对"+"运算符进行重载。否则直接使用"+"运算符会出现错误。

1.基本内置类型

C++ 定义了一组表示整数、浮点数、单个字符和布尔值的算术类型(arithmetic type),另外还定义了一种成为void的特殊类型。void类型没有对应的值,仅在有限的一些情况下,通常用作无返回值函数的返回类型。

C++:算术类型(32位机)
类型

存储空间
bool
布尔型
——
char
字符型
8bit(1Bytes)
wchar_t
宽字符型
16bit(2Bytes)
short
短整型
16bit(2Bytes)
int
整型
16bit(2Bytes)大多数机器实际为32bit(4Bytes)
long
长整型
32bit(4Bytes)
float
单精度浮点型
32bit(4Bytes)6位有效数字
double
双精度浮点型
64bit(4Bytes)10位有效数字
long double
扩展精度浮点型
10位有效数字(通常为3到4个机器字)
关于C++算术类型使用的

1)要注意把负值赋值给unsigned对象,在C++中是完全合法的,其结果是该负数对该类型取值个数求模后的值;

2)实际上通用机器都是用和long类型一样长的32位来表示int类型,二者选择需要事先了解实际运行时性能的代价;

3)关于浮点型的选择:使用double类型基本不会错,float类型可能精度不够且计算要比double慢的多,long double提供的精度大多数时候都没有必要,而且还要承担额外的运行代价。

2.字面值常量(literal constant)

像42这样的值,在程序中被当做字面值常量(literal constant)。称之为字面值是因为只能用它的值来称呼它,称之为常量是因为它的值不能修改。每个字面值都有其相应的类型,例如:0是int型,3.14159是double型。只有内置类型存在的字面值,比如整型字面值、浮点型字面值、布尔字面值、字符字面值、字符串字面值,没有类类型的字面值,因此也没有任何标准库类型的字面值。下面是C++字面值常量的示例程序:

#include <iostream>
int main(){
    s\
std::cout << "1"
                 "2"
                 "3"
              <<std::endl;

    /*************CHAR****************/
    'A';//char
    L'a';//wchar_t
    /*************INTRGER****************/
    int a=20;//decimal int
    int b=024;//octal int
    int c=0x14;//hexdecimal int
    std::cout << a << b << c <<std::endl;
    128u;//unsigned
    1024UL;//unsigned long
    1L;//long
    8Lu;//unsigned long
    /*dont have short literal constant*/
    /**************BOOL***************/
    bool test_bool = false;
    std::cout << test_bool <<std::endl;
    /**************FLOAT***************/
    3.14159F;
    3.14159E0f;
    .001f;
    1E-3F;
    12.345L;
    1.2345E1L;
    0.;
    0E0;
    /**************ESCAPE SEQUENCE***************/
    std::cout << "a\n"
                 "bcde\t"
                 "efg\v"
                 "hijkl\b"
                 "mn\r"
                 "opq\f"
                 "rst\a"
                 "uvw\\"
                 "x\?"
                 "y\'"
                 "z\""
                 "abc\7"
                 "\12efg"
                 "\40hijkl"
                 "\062\115\0"
                 <<std::endl;

    return 0;
}

上面的示例程序,除了展示了C++内置类型字面值规则,还讨论了字符字面值中非打印字符的转义字符序列、字符串字面值的连接以、多行字面值。

3.变量

变量提供了程序可以操作的有名字的存储区。C++中每一个变量都有特定的类型,可以把一个变量看成是一个内置类型的实例化对象,变量所属的内置类型决定了变量的内存大小和布局、能够存储于该内存中的值的取值范围以及可应用在该变量上的操作集。变量是左值(lvalue),它可以出现在赋值语句的左边或者右边。但字面值是右值,它只能出现在赋值语句的右边,因此不能赋值。

变量名即变量的标识符(identifier),可以由字母、数字和下划线组成。变量名必须以字母或下划线开头,并且区分大小写字母:C++中的标识符都是大小写敏感的。C++中保留了一组词用作该语言的关键字,关键字不能用作程序的标识符,下表列出了C++所有的关键字。

C++关键字一览表
asm
do
if
return
try
auto
double
inline
short
typedef
bool
dynamic_cast
int
signed
typeid
break
else
long
sizeof
typename
case
enum
mutable
static
union
catch
explicit
namespace
static_cast
unsigned
char
export
new
struct
using
class
extern
operator
switch
virtual
const
false
private
template
void
const_cast
float
protected
this
volatile
continue
for
public
throw
wchar_t
default
friend
register
true
while
delete
goto
reinterpret_cast


此外,C++还保留了一些词用作各种操作符的替代名,用于支持某些不支持标准C++操作符号集的字符集。如下表,它们也不能用作标识符。

C++ 操作符替代名
and
bitand
compl
not_eq
or_eq
xor_eq
and_eq
bitor
not
or
xor


除了关键字,C++还保留了一组标识符用于标准库,标识符不能包含两个连续的下划线,也不能以下划线开头后面紧跟一个大写的字母。有些标识符(在函数体外定义的标识符)不能以下划线开头。

变量的命名有许多被普遍接受的习惯,遵循这些习惯可以提高程序的可读性。

  • 变量名一般用小写字母;
  • 标识符应该使用能帮助记忆的名字,比如on_loan或salary;
  • 包含多个词的标识符可以使用下划线或者驼峰书写方式,比如student_loan或studentLoan。

变量的声明和定义:C++通常是由许多文件组成的,为了让多个文件访问相同的变量,C++区分了声明和定义。变量的定义(definition)用于为变量分配存储空间,还可以为变量指定初始值,需要注意的是在一个程序中,变量有且仅有一个定义。而声明(declaration)用于向程序表明变量的类型和名字。定义也是声明:当定义变量时,我们声明了它的类型和名字。可以通过extern关键字声明变量名而不定义它,例如

extern int i;//declares but does not define i
int i;//declares and defines i

extern 声明不是定义,也不分配存储空间,事实上它只是说明了变量的定义在程序的其他地方。程序中变量的声明可以有多次,而只能定义一次。通常把一个对象的定义放在它首次使用的地方是一个很好的办法,可以提高程序的可读性。

C++是一门静态类型语言,在编译时会做类型间差,程序在使用变量前必须先定义变量的类型。

变量的初始化:当定义没有初始化的变量时,系统有时候会帮我们初始化变量,其中内置类型变量的初始化,取决于变量定义的位置,在函数体外定义的变量都初始化成0,在函数体里定义的内置类型变量不能自动进行初始化。对于类类型变量来说,它可以通过一个或多个构造函数来控制类对对象的初始化。

名字作用域:用来区分名字的不同意义的上下文称为作用域(scope)。C++中大多数作用域用花括号来界定,可分为全局作用域(global scope)、局部作用域(local scope)、语句作用域(statement scope)。C++的作用域还可以嵌套,内部作用域中名字可以屏蔽外部作用域中的名字。

#include <iostream>
#include <string>
std::string s1= "hello";
int main(){
    std::string s2 = "world";
    std::cout << s1 << " " << s2 << std::endl;
    int s1 = 42;
    std::cout << s1 << " " << s2 << std::endl;
    return 0;
}
上面这段程序的输出是hello world 以及 42 world,可见局部变量s1的定义屏蔽(hide)了全局变量s1。

4.const限定符

举个例子,下列for循环语句有两个问题,两个都和使用512作为循环上界有关。

for(int index=0; index != 512;++index){
//...
}
第一个问题,程序的可读性很差。512是凭空出现的,它的意义在上下文没有体现出来,在这里512我们称之为魔数(magic number);

第二个问题,程序的可维护性差,假设这个程序非常的庞大,512出现了100次用于表示特殊缓存区的大小,现在我们要把缓冲区的大小增大到1024,要实现这一改变,必须检查每个512出现的位置。我们必须要确定哪些512表示缓冲区大小,而哪些又不是。改错一个都会使程序崩溃。

那么很自然,我们会想到使用一个初始化为512的对象来解决这个问题。

int bufSize = 512;
for(int index = 0; index != bufSize; ++index){
//...
}
通过很好记的名字bufSize,增强了程序可读性,也减少了程序维护的工作量。但是定义一个变量代表某一常数的方法仍然有一个严重的问题。即bufSize是可以被修改的,buSize可能被有意或无意的修改,这是非常危险的。这时我们的const限定符就出现了,它可以把一个对象转换成一个常量。
const int bufSzie = 512;
定义bufSzie为常量并初始化为512,变量bufSize仍然是一个左值,但是现在这个左值是不可以修改的,任何修改bufSize的尝试都会导致编译错误,因此const常量在定义时必须要初始化。const对象默认为文件的局部变量,在全局作用域定义非const变量时,它在整个程序中都可以被访问。我们可以把一个非const变量定义在一个文件中,假设已经做了合适的声明,就可以在另外一个文件中使用这个变量:
//file_1.cpp
int counter; //definition
//file_2.cpp
extern int counter; //declares uses counter from file_1
++counter; //increments counter defined in file_1
而全局作用域声明的const变量是定义该对象的文件局部变量,此变量只存在于那个文件中,不能被其他文件访问。但是通过指定const为extern,就可以在整个程序中访问const对象了:
//file_1.cpp
extern const int bufSizer = fcn();
//file_2.cpp
extern const int bufSize;// uses bufSize from file_1
//uses bufSize defined in file_1
for(int index = 0; index  != bufSize; ++index){
//...
}
简而言之:全局作用域定义的非const变量默认为extern,要使全局作用域定义的const变量也能够在其他文件访问,必须显式的指定它为extern。

5.引用

引用(reference)就是对象的另一个名字,在实际程序中,引用主要用作函数的形式参数。引用是一种复合类型(compound type),通过在变量名前添加"&"符号来定义。所谓的复合类型是指用其他类型定义的类型。每一种引用类型都“关联”到某一其他类型,不能定义引用类型的引用,但可以定义其他类型的引用,引用必须用与该引用类型相同的对象初始化。

int ival =42;
int &refVal = ival;//ok: refVal refers to ival
int &r1 = 42;//error:initializer must be an object
int &r2;//error: a reference must be initialized
const int jval = 100;//const object
const int &r3 = 42;//ok
const int &r4 = r + ival;//ok
const int &r5 = jval;//ok
int &r6 = jval;//error:nonconst reference to a const object
1)引用是别名。当引用初始化后,只要该引用存在,它就绑定到初始化时指向的对象,不可能在将引用绑定到另一个对象;

2)可以在一个类型定义行中定义多个引用,但是每个引用标识符前必须都加上"&"符号;

3)const引用是指向const对象的引用,const引用可以初始化为不同类型的const对象或者初始化为右值如字面值常量。

6.typedef名字

tyedef可以用来定义类型的同义词,typedef名字可以用作类型说明符。typedef定义以关键字typedef开始,后面是数据类型和标识符。标识符或类型名并没有引入新的类型,而只是现有数据类型的同义词。typedef名字可以出现在程序中类型名可出现的任何位置。

typedef double wages; // wages is a synonym for double
wages hourly, weekly; // double hourly, weekly
typedef 通常被用于以下三种目的:
  • 为了实现隐藏特定类型的实现,强调使用类型的目的。
  • 简化复杂的类型定义, 使其更易理解。
  • 允许一种类型用于多个目的,同时使得每次使用该类型的目的明确。

7.枚举(enum)类型

我们经常需要为某些属性定义一组可选择的值。例如文件打开的状态可能会有三种:输入、输出和追加。记录这些状态值的一种方法是每种状态都与一个唯一的常数值相关联。我们可能会这么编写代码:

const int input = 0;
const int output = 1;
const int append = 2;
这种方法虽然也能奏效,但它有一个明显的缺点:没有指出这些值是相关联的。枚举(enumeration)提供了一种替代方法,不但定义了整数常量集,而且还把它们聚集成组。

枚举的定义包括关键字enum,其后是一个可选择的枚举类型名,和一个用花括号括起来、用逗号分开的枚举成员(enumerator)列表:

//input is 0, output is 1, and append is 2
enum open_modes {input,  output, append};
默认的,第一个枚举成员赋值为0,后面每个枚举成员赋的值比前面大1。

枚举成员是常量:可以为一个或多个枚举成员提供初始值,用来初始化枚举成员的值必须是一个常量表达式(constant expression)。常量表达式是编译器在编译时能够计算出结果的整型表达式。整型字面值常量是常量表达式。

//shape is 1, sphere is 2, cylinder is 3, polygon is 4
enum Forms {shape = 1, sphere, cylinder, plolygon}
// a is 2, b is 3, c is 3 ,d is 4
enum Points {a=2, b, c=3, d}
定义完一个枚举类型后,不能改变其枚举类型的值。枚举类型本身就是一个常量表达式,所以也可以用于需要常量表达式的任何地方。

每个enum都定义一种唯一的类型。和其他类型一样,可以定义和初始化Points类型的对象,也可以以不同的方式使用这些对象。枚举类型的对象的初始化或赋值,只能通过枚举成员或同一枚举类型的其他对象来进行。

#include <iostream>
int main(){
    enum Points {a=2,b,c=3,d};
    std::cout << a << ","
              << b << ","
              << c << ","
              << d
              <<std::endl;
    Points p1 = d;
    Points p2;
    p2 = p1;
    std::cout << p1 << std::endl;
    std::cout << p2 << std::endl;
    return 0;
}
程序输出:
2,3,3,4
4
4

8.类(class)类型

C++ 中,通过定义类(class)来自定义数据类型。类定义了该类型的对象包含的数据和该类型的对象可以执行的操作。标准库类型string、istream和ostream都定义成类。

class Sales_item {
public:
    // operations on Sales_item objects will go here
private:
   std::string isbn;
   unsigned units_sold;
   double revenue;
};
类定义以关键字class开始,其后是该类的名字标识符。类体位于花括号里面,花括号后面必须跟一个分号。

从操作开始设计类:每个类都定义了一个接口(interface)和一个实现(implementation)。接口由使用该类的代码需要执行的操作。实现一般包括该类所需要的数据,还包括定义该类需要的但又不供一般性使用的函数。定义类时,通常先定义该类的接口,即该类所提供的操作。通过这些操作,可以决定该类完成其功能所需要的数据,以及是否需要定义一些函数来支持该类的实现。

类可以为空,类体定义了组成该类型的数据和操作。这些操作和数据是类的一部分,也称为类的成员(member)。操作称为成员函数,而数据则称为数据成员(data member)。类也可以包含0个到多个private或public访问标号(access label)。访问标号控制类的成员在类外部是否可以访问,使用该类的代码可能只能访问public成员。

使用struct关键字:C++支持另一个关键字struct,也可以定义类类型。struct关键字是从C语言中继承过来的。用class和struct关键字定义类的唯一差别在于默认访问级别。默认情况下,struct的成员为public,而class的成员为private。

9.编写自己的头文件

由多个文件组成的程序需要一种方法连接名字的使用和声明,在C++中这是通过头文件来实现的。为了允许把程序分成独立的逻辑块,C++支持所谓的分别编译(separate compilation)。头文件为相关声明提供了一个集中存放的地方,头文件一般包括类的定义、extern变量的声明和函数的声明。头文件的正确使用能够带来两个好处:保证所有文件使用给定实体的同一声明;当声明需要修改时,只有头文件需要更新。当设计头文件时需要注意一下几点:

第一、头文件用于声明而不是定义。但有三个例外,头文件可以定义类、值在编译时就已知的const对象和inline函数。这些实体可以在多个源文件中定义,只要每个源文件中的定义是相同的。

第二、使用预处理器定义头文件保护符避免多重包含。

#ifndef SALESITEM_H
#define SALESITEM_H
    // Definition of Sales_item class and related functions goes here
#endif

第三、如果头文件在<>里,那么认为该头文件是标准头文件,如果在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。

(完)

56.8K

发表评论:

© 云中布衣 2015 | Driven by EMLOG  | SiteMap | RunTime: 11.62ms&RSS  | MORE  |   | TOP

文章数量【258】 评论数量【238】 稳定运行【1210天】

Visitor IP Address【54.82.73.21】

Email:ieeflsyu#outlook.com