[C++]语言基础

Posted jiangwei0512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++]语言基础相关的知识,希望对你有一定的参考价值。

基本内置类型

相比于C语言增加的一些类型:

 

类型

 

含义

 

示例

 

bool

 

布尔类型

 

true、false

 

wchar_t

 

宽字节

 

L'a'、L'你'

 

char16_t

 

Unicode字符

 

u'a''、u'你'

 

char32_t

 

Unicode字符

 

U'a''、U'你'

字面值

指针字面值:nullptr。C语言中有预定义的NULL,其实就是个0。C++中也有NULL(书中介绍NULL的定义在<cstdlib>中,但是没有找到,而且不包含它也可以使用NULL),但是不建议使用,最好使用nullptr。

bool类型的字面值只有true和false。C语言是没有布尔类型的,所谓的“布尔类型”其实是整型,就是1相当于true,0相当于false。

变量初始化

C++中的初始化异常复杂,且初始化和赋值是两个完全不同的操作。下面是对一个int类型的变量赋值的几种方式:

int v1(1024);    // direct-initialization, functional form
int v2{1024};    // direct-initialization, list initializer form
int v3 = 1024;   // copy-initialization
int v4 = {1024}; // copy-initialization, list initializer form

这四种方式都是对变量的初始化。其中有“{}”的被称为列表初始化。使用“=”的是拷贝初始化,而使用“()”或者“{}”的是直接初始化。具体两者有什么差别,目前还不知道。

如果定义变量时没有指定初值,则变量被默认初始化。如果是内置类型的变量未显式初始化,它的值由定义的位置决定,定义于任何函数体之外的变量被初始化为0,在函数体内部的内置类型变量将不被初始化(类定义体内也一样)。每个类各自决定其初始化对象的方式。但是实际测试似乎也会被初始化为0:

void func1(void)
{
    int a;
    double b;
    cout << a << endl;
    cout << b << endl;
}

class A {
public:
    int a;
};

void func2()
{
    A a;
    cout << a.a << endl;
}

上述打印的结果都是0,似乎看不出来是未被初始化,这应该是跟编译器有关,最好还是不要这样做,以防导致问题。

引用

C++包含内置基本类型和复合类型,复合类型有指针和引用。其它的C语言基本都有,引用是C++新引入的。引用有右值引用和左值引用,一般引用指的是左值引用,这里讲的也只是左值引用。

  • 引用为对象(内置类型也算对象)起了另外的一个名字。

  • 引用必须初始化(一般在初始化变量时,初始值会被拷贝到新建的对象中;然而定义引用时,是要把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用)。

  • 引用一旦绑定,不可再次绑定给其它对象。

  • 对引用的操作都是在与之绑定的对象上进行的。

  • 引用不是对象,所以没有引用的引用!

  • 引用标识符以“&”开头。

  • 引用只能绑定在对象上,而不能与字面值或者表达式的计算结果绑定在一起(后面在引入const之后会介绍一种例外)。

引用的代码示例:

int main()
{
    int i = 0, &ri = i;  // ri is a reference to i
    // ri is just another name for i;
    // this statement prints the value of i twice
    std::cout << i << " " << ri << std::endl;
    
    i = 5; // changing i is reflected through ri as well
    std::cout << i << " " << ri << std::endl;
    
    ri = 10; // assigning to ri actually assigns to i
    std::cout << i << " " << ri << std::endl;
    
    return 0;
}

const

C++上的const比较复杂。最简单的:

const int i = 1;  // must be initialized.

const对象具有跟C语言中的宏相同的功能,编译器将在编译过程中把用到该变量的地方都替换成对应的值。事实上,C++中应该使用const、enum和inline函数等来替代C语言中通过#define定义的宏。

默认状态下,const对象仅在文件内有效。如果要在多个文件中声明并使用它,可以增加extern关键字。这时const对象可以不初始化。

// declare
extern const int bufSize;

// define
extern const int bufSize = 512;

对const的引用

对const的引用也称常量引用,但是这个说法容易引起误解,因为引用是其它对象的别名,且初始化之后就不会被修改,也就是说作为“引用”本身就是常量。这里用到常量引用需要特别的注意是指对const对象的引用。

通常情况下,引用的类型必须与其所引用的对象一致,但是由于const的存在,就导致了一种例外(还有一种例外暂时不说明),即初始化常量引用时可以用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型。下面时一些例子:

int main()
{
    const int i = 1;        // must be initialized.
    int a = 42;
    const int &ra1 = a;     // int -> const int.
    const int &ra2 = 42;    // int -> cont int, literal is ok.
    // int &r = 42;         // this is wrong.
    const int &ra3 = ra1 * 2;

    // ra1 = 24;            // this is wrong.
    a = 54;
    cout << ra1 << endl;    // 54

    return 0;
}

常量引用表明了引用指向的对象是常量,所以不会被改变,所以即使是使用42这种字面值也不会有问题。对const的引用可以引用一个并非const的对象,比如这里的a是可变的,但是通过ra1却不能改变它。

指针和const

有两种组合:

  • 指向常量的指针:指针指向的是常量,const在*之前。

  • 常量指针:指针本身是常量,const在*之后。

示例:

int main()
{
    const int i = 42;
    const int *pi1 = &i;     // pointer to const.
    // int *pi1 = &i;        // this is wrong.
    int j = 42;
    const int *pj1 = &j;     // pointer to non-const is ok.
    // *pj1 = 24;            // but we can't change value by pj.
    // int * const pi2 = &i; // error: const int * -> int *.
    int * const pj2 = &j;
    
    return 0;
}

上例中错误的*pi1和*pi2其实本质是一样的,都是const int *无法转换成int *。

由此又引出const的两个分类:

  • 顶层const:对象本身是常量。

  • 底层const:指向的对象是常量。

示例:

int main()
{
    int i = 0;
    int *const p1 = &i;         // 不能改变p1的值,顶层const.
    const int ci = 42;          // 不能改变ci的值,顶层const.
    const int *p2 = &i;         // 可以改变p2的值,底层const.
    const int * const p3 = p2;  // 前者是底层const,后者是顶层const.
    const int &a = ci;          // 用于声明引用的cosnt都是底层const.

    return 0;
}

常量表达式

常量表达式是指不会改变并在编译过程中就能得到计算结果的表达式。在C++中,可以将变量声明为constexpr类型,以便由编译器来验证变量的值是否是一个常量表达式。在代码编写中,如果你想要将一个变量设定为常量表达式,最好就是将它声明为constexpr。示例:

constexpr size_t rowCnt = 3, colCnt = 4;
constexpr int *p = nullptr;
// p = &i;                  // 不能再改变了.

声明constexpr时用到的类型一般比较简单,值也显而易见,容易得到,因此把它们称为字面值类型。算术类型(布尔类型、整型、浮点型)、引用和指针,都是字面值类型。

一个constexpr指针的初始值必须时nullptr或者0,或者是存储于某个固定地址中的对象。在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。

常量表达式可以是constexpr函数。这种函数的返回值类型以及所有的形参都是字面值类型,而且函数体中必须有且只有一条return语句。示例:

constexpr int new_sz() { return 42; }

auto和decltype

auto能让编译器替我们分析表达式所属的类型。编译器推断出来的auto类型有时候和初始值的类型并不完全一样。通常auto会忽略顶层const,但保留底层const。

为什么要引入auto,它的具体应用场景是什么,目前还不是很确定。有一些简单的情况,使用auto可能更加的方便:

int main()
{
    std::vector<std::string> vec;

    for (std::vector<std::string>::iterator i = vec.begin(); i != vec.end(); i++)
    {
        // do something.
    }
    for (auto i = vec.begin(); i != vec.end(); i++)
    {
        // do something.
    }

    return 0;
}

显然这里使用第二种方式更简洁明了。auto在模板上似乎也有应用,但是目前还不知道,以后再说明。

表达式是最小的计算单元。一个表达式包含一个或多个对象,通常还包含一个或多个运算符。表达式求值会产生一个结果。有时候我们希望从表达式的类型推断出要定义的变量的类型,但是又不想用该表达式的值初始化变量(如果想的话可以直接用auto),就可以使用到decltype。

decltype接收一个表达式,并返回其类型。编译器分析表达式的类型,但不实际计算表达式的值。decltype返回表达式的类型,也包括顶层const(跟auto不一样)和引用。

decltype的参数可以是变量。如果decltype接收的表达式不是一个变量,则返回表达式结果对应的类型。对于decltype所用表达式来说,如果变量加上了一对括号,则得到的类型与不加括号时会有不同。如果给变量加上一层或多层括号,编译器就会把它当成一个表达式。decltype((variable))的结果永远是引用类型。

示例:

int main()
{
    int a = 0;
    decltype(a) c = a;   // c is an int
    decltype((a)) d = a; // d is a reference to a
    ++c;                 // increments c, a (and d) unchanged
    cout << "a: " << a << " c: " << c << " d: " << d << endl;
    ++d;                 // increments a through the reference d
    cout << "a: " << a << " c: " << c << " d: " << d << endl;

    int A = 0, B = 0;
    decltype((A)) C = A;   // C is a reference to A
    decltype(A = B) D = A; // D is also a reference to A
    ++C;
    cout << "A: " << A << " C: " << C << " D: " << D << endl;
    ++D;
    cout << "A: " << A << " C: " << C << " D: " << D << endl;

    int i = 42, *p = &i, &r = i;
    decltype(r + 0) b;     // r + 0是表达式,类型是int,所以b是int.
    // decltype(*p) c;     // c的类型是int&,引用必须要初始化.

    return 0;
}

这里比较奇怪的是*p的类型是int&。这里的原因是:如果decltype接收的表达式的求值结果是左值,则得到引用类型。

另外,虽然decltype的使用跟函数一样,但是它是类型指示符,并不是函数。

左值和右值

左值和右值并不是C++才有的,C语言也有。左值可以位于赋值语句的左边和右边,而右值只能在右边。另一种说法:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。所以上例中的*p,为的是p的身份,即内存位置,所以decltype的结果是引用。

sizeof

sizeof的两种用法(C语言应该只有第一种):

int main()
{
    int a = 42;
    cout << sizeof (a) << endl;
    cout << sizeof a << endl;

    return 0;
}

有没有括号都可以。跟decltype一样,第一种使用方式有点像函数,但是sizeof是运算符,不是函数。C语言中用的是第一种,C++中还可以不加括号使用。

关于sizeof运算各种类型的结果:

  • 对char类型使用得到的结果是1。

  • 对引用类型使用得到的是被引用对象所占用的大小。

  • 对指针使用得到指针本身所占用的大小。

  • 对解引用指针使用得到指针指向的对象所占用的大小,指针不需要有效。

  • 对数组使用得到整个数组所占的大小。

  • 对string对象或vector对象使用返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。

其实除了最后一条(C语言中也没有string对象和vector对象),其它跟C语言也一致。下面是一个string的例子:

cout << sizeof (string("Jack")) << endl;
cout << sizeof (string("Jackson")) << endl;

两者得到的值都是32。

强制转换

C++为强制转换引入了运算符:

  • static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用。

  • dynamic_cast:暂时未介绍。

  • const_cast:只能改变运算对象的底层const。

  • reinterpret_cast:非常危险,最好不要用。

C++中也支持C语言中的强制类型转换,但是最好不要用。对于隐式的类型转换,跟C语言也差不多。

控制语句

C++中有新的for语句用法:

int main()
{
    vector<int> v = {1, 2, 3};
    for (auto &r : v) {
        cout << r << endl;
    }

    return 0;
}

C++中对异常的处理,增加了throw、try、catch和一系列的异常类,示例:

try {
    if (item1.isbn() != item2.isbn()) {
        throw runtime_error("Data must refer to same ISBN");
    }
} catch (runtime_error err) {
    cout << err.what() << endl;
}

异常对象定义在<stdexcept>:

以上是关于[C++]语言基础的主要内容,如果未能解决你的问题,请参考以下文章

有趣的 C++ 代码片段,有啥解释吗? [复制]

以下代码片段 C++ 的说明

C++ 代码片段执行

此 Canon SDK C++ 代码片段的等效 C# 代码是啥?

C++ 代码片段(积累)

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情