C++ Primer 第二章 变量和基本类型

2.1 基本内置类型

    基本内置类型是C++“自带”的类型,区别于标准库定义的类型。使用时不需要应用标准库就可以使用,我们可以理解为数字型有下面这些

    整形:就是整数或者是没有小数位的数。它包括bool(0,!0) , char, wchar_t(非标准英文字符用char无法全部表现所以需要这个类型来表示),short, int ,long。 整形中除了bool外其他类型可以是带符号,也可以是无符号的,无符号的取值范围不能为负,有符号取值可以有正有负,但由于有符号数所占位其中一位是符号位所以它的正数取值范围要比无符号范围小一位。

    浮点型:就是带小数的数,包括float , double , long double他们之间的区别是取值范围和精度,可以根据你的需要选择合适的类型。

    char , wchar_t 两个类型虽然都是字符型但区别比较大:

    在宽度上来说,一个是1byte,一个是2byte(在linux上实际是4byte) 
    在编码上来说 wchar_t表示unicode编码方式

    以上不同说明 wchar_t 可以包含更多内容比如中文,日文等等。

 

2.2 字面值常量

    就是常量本身,比如 21, “name” 表示类两种类型的字面值。有些类型有多重表示法,比如数字可以用不同进制表示,浮点型可以用科学计数法书写等等。

 

2.3 变量

    变量是对数据的有名存储,既然有名就可以对改数据做一些操作如赋值修改等等。这里有两个表达式概念先了解一下

    左值:左值可以出现在赋值语句的左面或者右面

    右值:右值只能出现在赋值语句的右面儿不能出现在左面

    变量是左值,常量是右值,字面值常量也是右值

 

    变量定义和初始化值更具类型不用也不同,内置类型不能隐身初始化,类类型一定要可以隐式初始化。

    先看内置类型:

    int a ; // 没有初始化内容,它的值由系统分配规则是在栈区和堆区(函数内定义或者类里面定义)都取随机值,在全局区(全局常量,静态变量)都是全零值。

    int b=1 ; // 这种叫赋值初始化

    int c(1),d(b + 2) ; // 这种叫直接初始化()中不一定是常量可以是一个表达式或变量

    这两种初始化是不一样,后面讲到类类型时就会明白一般来说直接初始化更灵活且效率更高。
    需要特别强调的是未初始化的内置类型虽然也会有值但是其分配规则导致其值不确定性,所以我们定义内置类型时一定要初始化值而不依赖系统的内部非配规则。

 

    再看类类型定义:

    string a; // 隐式初始化初始化成了 “” 

    string b = "name" ;

    string c(b) ;

    string d(10,'a') ; // string类特有的初始化方法,等同于10个字符a组成字符串并赋值初始化给变量b

    类类型的初始化其实就是构造函数, 隐式初始化实际上就调用类型的默认构造函数来初始化类型对象;直接初始化是调用类的拷贝构造函数;而直赋值初始化是调用赋值操作符重载和拷贝构造函数。相关内容再后续章节会有详细说明。

 

    声明和定义

    c++是一门奇怪的语言,我们先看如下代码

int void main()
{    
    std::cout << v1 << std::endl;
}

int v1 = 1;

 

    绝大部分语言中这样的写法是没有问题的,但在C++中却会编译不通过,因为C++是严格的顺序编译非类对象代码的,当main函数编译时要用到的变量v1还没有执行定义,c++就会抛出异常说使用了未定义的变量v1,如果吧这个 int v1;语句放到函数前面就没问题。那么我们是不是只能这样做呢? 答案当然是否定的,我们可以不改变现在代码顺序而使用声明解决这个编译错误,可以这样修改代码:

int void main()
{    
    extern int v1;
    std::cout << v1 << std::endl;
}

int v1;

    注意红色新增代码就是一个声明。他的意思是说:HI,系统中的某个地方定义了一个叫v1的int变量,所以你可以放心的使用。

    声明是告诉编译器一些信息所以不会分配内存空间也不会产生具体的数据。 声明的对象一旦被使用就一定要在某个地方定义它,否则编译器即使暂时不抛出异常在编译完毕后发现根本没有地方找到相关定义也一样会编译失败。

    以上例子只适合变量,同一个文件中常量在使用前一定要先定义,否则即使做了申明也会出错。

 

    声明最大的好处体现在多文件的编程中,比如说我编写了一个头文件并引入到了主文件中,头文件需要使用主文件定义的变量。由于头文件一般是先于主文件编译执行的,所以就会出现未定义的编译错误,如果在头文件中先声明一下这变量再使用就没有问题了。  除了变量C++还有常量(后面会讲到),它和变量有一些不同

// head.h
extern int a;
// usend a


extern const int b; // 常量声明语法也可写成 const extern int b
// usend b

    头文件中用到了两个对象,但他们是在其他地方定义的所以我们需要申明一下在使用。

//main.cc
#include head.h

int a(1); // 这样定义的变量可以被其他相关文件(head.h)声明并使用

// const int b(2); 对于常量这样写不行是因为这样定义的变量作用范围只在本文件中,头文件虽然做了声明但无法使用这个常量,应该用下面的语法来定义
extern const int b(2); // 这样定义的常量才可以被其他相关文件(head.h)声明并使用

int main()
{
    // do...

    主文件中定义了头文件需要的对象,和变量不同,常量如果在其他地方被申明和使用一定要在定义的时候加上关键字extern因为如果不加的话常量默认作用范围是定义它的那个文件,变量默认是所有相关文件。

   

    虽然C++支持面向对象,但他并不是完全的面向对象他也支持面向过程。因此有一些代码是不属于任何类的,比如说main 函数就不属于任何类。

    针对这种情况变量根据其位置可分为两种 全局变量局部变量。广义上说定义在类内部(包括类所属函数内部)和函数内部的变量叫局部变量,除此之外的变量自然就是全局变量了。

 

2.4 const限定符

    限定符表示定义一个常量,常量一但定义则不可更改。不可更改的意思是既不能对常量句柄句柄重新赋值也不能更改常量对象的数据成员(如果是一个自定义类常量C#中是可以更改类成员的但C++不允许这么做)。

    常量分为两类 编译时常量运行时常量(在.NET中标示为readonly) 编译时常量是定义后直接初始化的常量,运行时常量值要初始化的值必须要通过代码运行才可以确定的

const int a(1); // 编译时常量

const int b = getval(); // 运行时常量值来自一个函数的运行结果

const myclass my(1,"tom"); // 自定义类的常量定义都是运行时常量,因为需要运行类的构造函数

    常量初始化之后就不可以做任何的更改操作。

 

2.5 引用

    引用就是对某个对象的另外一个别名。引用最重要的作用是函数传参。

变量引用
int val = 1;

int &refval = val; // 引用了一个变量


int &refval1 = 12;  // 错误,12是个字面常量,常量必须要用常量引用

refval = 2;  // 等价于 val = 2



常量引用
const int val = 1;

const int &ref1 = val; // 引用了一个一般常量


const int &ref2 = 12// 引用了一个字面常量

int &ref3 = val// 错误,常量必须要使用常量引用,ref3是个变量引用


int var = 2;
const int &ref2 = var // 常量引用指向了一个变量,这时候  

refval2 = 3;  // 不允许通过常量引用来做任何更改操作
varval = 3;   // 但是可以用原始变量来更改内容

    总之常量引用可以引用常量或变量,但是无论如何都不能通过引用来更改数据内容。

    变量引用只能引用变量,用引用可以更改内容效果与用原始变量更改内容是一样的。

    

    对于引用还有一点很重要:非常量引用类型必须严格匹配,常量引用可以在内置类型之间相互引用

double a = 123.4;

int &b = a; // 错误,类型不匹配


const int &c = a; // ok
// 这个操作实际等同于

int temp = a;
const int &c = temp;

    如果对非const引用b不做类型匹配限制,b实际就会引用临时变量temp,对b的修改无法反应到变量a,引用失去了其意义。 const引用c没有这样的问题,因为它本身不允许修改。

 

2.6 typedef

    这个关键字非常有用,用来给某个类型指定一个别名,比如

    typedef int zx ;

    int a(1) ; // 等同于 zx a(1)

    在后面我们可以看到使用一些复杂类型或是函数指针时改关键字可以大大减少你的代码整洁度。

 

2.7 枚举

    枚举是一组可选常量值,既然是一组可选值说明包含多个常量。枚举定义语法如下

    enum val{val1 = 2, val2 = 4, val3}  // 最后一个内容没有显示给值等价于 val3 = 5 

    如果不指定值默认第一个值从0开始下一个依次+1递增。

 

    枚举的每一项都是一个唯一的const类型值,上面的定义有点类似于

    const val1 = 2; const val2 = 4; const val3 = 5;

    由于是const的,所以 val2 = 1 或者 val a = 2; 都不允许。

   

    枚举项和int类型值有对应关系,但是二者只能单向转换,枚举可以自动转成int,而int却不能转成枚举

    val a = val2 ; // 枚举之间赋值初始化

    int b = val2 ; // 枚举转成int并初始化 

    val a = 2 ;     // int 不能转成枚举,无法初始化

 

2.8 类类型

    一般来说C++吧除了内置类型之外的类型都叫类类型,我们习惯上把自定义的类class称为类类型。 类一般采用先定义类并声明类的成员函数,然后在外部定义成员函数的语法形式。

    这部分内容不在这里详述会在后续章节中专门说明。

 

2.9 编写自己的头文件

    从上面的课程我们应该大概知道什么是头文件,一般来说头文件中包含声明而不是定义,但是下面两种情况比较特殊

    类定义要放在头文件里。这样如果某个文件需要这个类只需要把头文件include进来即可。   

    运行时常量也可以在头文件定义,表达式可以在包含文件中定义。这样就能实现在在不同的包含文件得到不同的常量值。

// head.h

int getval();

const int p = getval();


// mast1.cc 
#include "head.h"

int getval()
{
    return 100;
}

int main()
{
    cout << p << endl; // 输出100
}


// mast2.cc 
#include "head.h"

int getval()
{
    return 200;
}

int main()
{
    cout << p << endl; // 输出200
}

     有时候某个头文件可能会被包含多次(直接包含+间接包含)。比如文件包含了头文件A,也包含了头文件B,头文件B同时也包含了A,则文件重复包含了A。这个时候如果A中有定义语句就会产生“重复定义”的编译错误,这个时候可以用#ifndef 把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:
    #ifndef <标识>
    #define <标识>
    ...... 头文件代码
    #endif
    <标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h
    #ifndef _STDIO_H_
    #define _STDIO_H_

    ...... 头文件代码
    #endif
   

    该预定义标示不单能防止头文件被重复包含编译,而且还可以用在不同头文件定义同名对象时出现异常的处理上。

   

posted on 2012-04-28 18:09  老金  阅读(1771)  评论(1编辑  收藏  举报