c++ primer 概念整理第二章:变量与基本类型

本文深入探讨C++中的各种类型,包括基本内置类型、复合类型、自定义数据结构等,并详细讲解变量的定义、初始化及作用域等内容。

2.1基本内置类型:

2.1.1 算术类型

基本内置类型分为算术类型(字符,整数,布尔值,浮点数)和空类型(void).
掌握基本类型所占的位数(bool , char , wchar_t , char16_t , char32_t , short , int , long , long long , float (32bits), double (64bits), long double),在此指出double精度是10位有效数字,float是6位,二者运算效率差别不会很大.

.除去布尔和扩展的字符之外,其他的整型可以划分为带符号的和无符号的. int,short,long,long long用usigned关键字修饰,其中usigned int可以缩写为usigned.

字符型比较特别,分为 char , unsigned char , signed char 三种. 但是表现形式有两种,char表现为哪一种是由编译器决定的.最好明确指出.
.类型选择要注意一些问题

2.1.2 类型转换

非布尔算术值转为布尔值: 0转为false,否则为true;
布尔转非布尔: false 转0, true 转 1
浮点转整数: 截取整数部分
赋值给无符号型超出其表示范围值: 取模后取余数
赋值给带符号值时,结果未定义

含有无符号类型表达式:千万注意了下面小例子警示你:
unsigned u =10; int i =-42; usigned u1=20;
cout <

2.1.3 字面值常量

定义:这样的值一望而知,常量的形式和值决定了它的数据类型

整型和浮点型字面值: 例如:20(10进制),024(八进制),0x24(十六进制).,八进制和十六进制可能是带符号的,十进制字面值类型是能容纳其数值的int,long,long long中尺寸最小的.八进制和十六进制字面值类型是其能容纳其类型的,int,unsigned int , long , unsigned long , long long,unsigned long long中尺寸最小的.浮点字面值默认double,表现为一个小数或e表示指数的科学计数法.

字符和字符串字面值:单引号扩起来的是char字面值,双引号中0或多个字符是字符串字面值常量(常量字符构成的数组,最后还有一个空字符).

转义序列:不可打印字符需要用到转义符号’\’

指定字面值类型:加前缀或后缀例如 : L’a’(宽字符) u8(hi)utf-8字符串字面值. 42ULL(usigned long long) , 3,14159L(long double),最好大写L,小写容易和1混淆.

布尔值和指针字面值:true false nullptr.

2.2 变量

变量提供一个具名的可供程序操作的存储空间.每个变量有相关数据类型,决定了所占存储空间的大小,变量与对象(一块存储数据并具有某种类型内存空间)一般可以互换使用.对象能被程序修改数据,而值一般是只读的,并不严格区分

2.2.1 变量定义

类型说明符后面紧跟一个或多个变量名组成的列表.变量名以逗号分割.

初始值:对象获得一个特殊值表示这个对象被初始化了. 虽然可以用赋值符号=来初始化变量,但初始化与赋值并不是一个概念.赋值含义是当前值擦除用一个新值代替.而初始化是赋予一个新值.

列表初始化:看
int u =0 ; int u={0}; int u{0};int u(0);
都是正确的.但是如果:long double ld = 3.14;
int u{ld}, b={ld};//是错误的,因为由信息丢失风险.
int c(ld), d= ld; //正确,转换被执行.

默认初始化:变量没有指定初始值,执行默认初始化.初始化值和变量类型和所处的位置有关.例如内置类型若未显示的初始化,任何函数体之外的变量会被初始化为0,任何函数体内部内值变量不被初始化,试图拷贝或已其他方式访问该值将引发错误.
类自己定义是否允许不给初始值,如果允许它将决定对象的初始值是什么.类对象没有显示初始化其值由类决定.
例如string类默认初始化为一个空串.
建议初始化每一个内置类型变量.

2.2.2 变量声明与定义的关系

变量声明与定义的关系.声明让名字为程序所知,定义负责创建与名字关联的实体.
若声明一个变量,关键字extern,例如extern int i; 但是任何包含显示初始化的声明即定义,声明关键字失效.例如 extern double pi = 3.14 就是定义了.
变量只能一次定义,可多次声明.
此对分离式编译非常重要.编译阶段执行类型检查.

2.2.3 标识符

字母数字下划线组成,不能以数字开头.不能连续出现两个下划线,也不能下划线紧连大写字母开头(虽然编译能通过),函数体外的标识符不能以下划线开头.

命名规范:
变量名约定俗成规矩: 体现含义,变量名小写字母,用户自定义类型一般大写字母开头,多个单词组成的要区分. 若能坚持,必将有效. 不和关键字重复.

2.2.4 名字作用域

大多数以花括号分割.同名在不同作用域可能指向不同的实体. 全局作用域与块作用域.

作用域可以由嵌套关系,有外层作用域,内层作用域之分.允许内层作用域定义外层作用于已有的名字(覆盖)

2.3 复合类型

基于其它类型定义的类型,有两种重要的,引用和指针.

关于声明更一般的表述是,一条声明语句由一个基本的数据类型紧随其后的一个声明符列表组成. 基于基本数据类型可以生成更复杂的对象,并将其指定给变量名.

2.3.1 引用(重点)

c++ 定义了右值引用,13.6.1讲述,这里引用一般指左值引用.
引用即别名:
&d的形式来定义.
定义引用时,引用将与初始值绑定,无法将引用重新绑定到另一个对象,因此引用必须初始化.
对引用进行的操作都是在与其绑定的对象上进行的.
引用不是对象,不能定义引用的引用
引用的定义:
允许一条语句中定义多个引用,其中每个引用都必须以&开头.例如 int i= 100, &j = i, k =10,&l =k. 其中j l是引用,i k是int.
引用只能绑定到对象上,不能绑定到字面值或与某个表达式的计算结果绑定在一起. 类型必须匹配,
double dval= 10.2;int &x = dval;也是错误的.

2.3.2 指针(重点)

指针是指向另外一种类型的复合类型.指针区别于引用的地方在于.其一指针本身就是一个对象,允许赋值和拷贝,在指针的生命周期内可以指向不同的对象. 其二指针无需在定义时赋值,如果在块作用域内没有初始化将拥有一个不确定的值.

获取对象的地址:取地址符(&),例如;
int ival = 42 ;
int *p = &ival;
第二条语句把p定义为一个指向int的指针.随后初始化p令其指向名为ival的int对象.因为引用不是对象,没有实际的地址,不能定义指向引用的指针,但可以定义指向指针的指针.

除了2.4.2节和15.2.3节讲的以外,其他所有情况指针和所指的类型都要严格匹配.例如:
double dval;
double *pd = &dval;
double *pd2 = pd;

int *p = pd; // 错误,类型不匹配
p = &dval; // 错误

指针值应该属于下列状态之一:
指向一个对象
指向紧邻空间对象的下一个位置
空指针,不指向任何对象
无效指针,上述情况之外其他值.
知道指针指向是程序员的责任.

指针访问对象:
解引用符,(*),适用确实有效指向对象的指针,解引用会得到所指对象,对接引用结果赋值就是对指向的对象赋值.
* 和 & 的含义与上下文有关,具体参照primer page48

空指针

新标准是nullptr,原来是NULL,其值是0,NULL是预处理变量,预处理变量在编译时会被替换成实际值,避免使用NULL.
用int初始化一个指针是错误的,即便它恰好是0.

赋值与指针
赋值永远改变是等号左侧对象.而不是间接对象.

其他指针操作;
指针作为条件表达式时,当指针值为0时条件为false
指针比较操作比较的是地址

void*指针:
特殊指针,可存放任意类型对象的地址.但我们不知道地址中对象是什么类型,也就不能直接操作它指向的对象,19,1,1 4.11.3都会介绍.

2.3.3 理解复合类型的声明:

类型修饰符是声明符的一部分.

定义多个变量:
类型修饰符(&,)作用于当前的变量.int p1, p2; p1是指针,p2是int.坚持一种写法.

指针的指针:存放指针地址的变量.
指向指针的引用:指针是对象,存在指针的引用:例如:
int * p = nullptr;
int *&r = p;
理解第二条语句从右向左读,离变量名最近的有最直接的影响,首先r是一个引用,剩下确定应用的类型是什么 ,是int指针的引用.

2.4 const限定符

const限定的对象一旦被创建就不能被改变.所以const对象必须初始化 这样const int i ;是错误的.

初始化和const:
const限制只能在const类型对象上执行不改变内容的操作.像const int能参与基本的算数运算也能转换成一个布尔值.可以利用const初始化另一个对象.
默认状态下,const对象仅在文件内有效,const变量在编译时会被替换成相应的值.在多个文件出现同名const变量相当于在不同文件中定义了不同的变量. 如何避免多次定义相同的变量? 解决方法是在每个文件中不管是声明还是定义都加上extern关键字.

2.4.1 const引用

可以把引用绑定到const对象上.即对常量的引用.
例如: const int ci = 1024;const int & r = &ci;
引用绑定一个常量int,从右往左读,首先是一个引用,指向int的引用,指向const int的引用. 你不能通过r改变ci的值. 通常人们把对const的引用简称为”常量引用”,这一简称很靠谱,但是你得时刻提醒自己这只是个简称而已,严格说不存在常量引用因为引用并不是一个对象.

初始化和对const的引用:
引用的类型必须与其引用的对象的类型一致,但是有两个例外,第一种例外是初始化常量引用时候允许表达式作为初始值,只要该表达式的结果能够转化为引用的类型即可. 允许一个常量的引用绑定到一个非常量的对象上.
当常量引用被绑定到另一种类型时候会发生什么.
double dval = 3.14;
const int & ri = dval;// 正确会转化成const int temp = dval;const int & ri = temp 的形式
//int &r1 = dval;    // 报错,不会发生上述转换. 
return 0;

注意第二条语句绑定了一个临时量,而且不能通过它改变临时量,第三条语句绑定到一个临时变量上还试图改变它,简直毫无意义,所以第三条语句是非法的.

const引用可能引用一个并非const的对象,常量引用只对引用可参与的操作做除了限定,对于引用对象本身是否是常量未作假定,因为对象可能是个非常量,所以允许其他途径改变他的值,

2.4.2 指针和const

指向常量的指针不能用于改变所指对象的值,而且只能用指向常量的指针存放常量对象的地址. 反过来,指向常量的指针所指的对象不一定是个常量,而只是指针本身自觉不去改变所指对象的值.

const指针:指针与引用不同,指针是对象,所以指针本身可以是常量,即常量指针.一旦初始化完成后,它的值就不能再改了,把*放在关键字之前用以说明指针是一个常量:
int erorNum =0;
int * const curErr = & erorNum; //curErr是一个指向常量的指针.
const double pi = 3.14;
const double * const pip = & pi; //pi 是指向常量对象的常量指针
从右往左读, const直接限定变量名字,表明指针本事是一个常量.
curErr本身是一个常量,但是可以通过这个指针改变指向它的对象.

2.4.3 顶层const

指针本身是不是常量以及指针指向的是不是一个常量是两个相互独立的问题,用名词顶层const和底层const来区分.
更一般的顶层const可以表示任意的对象是常量,底层const则与指针和引用的复合类型部分有关,比较特殊的是指针类型既可以是顶层const,也可以是底层const.而用于声明引用的const都是底层const.
拷入拷出时候,顶层的const似乎没有什么太大的影响,对于底层的const,拷入拷出的对象必须具有相同的底层const资格,或者两种数据类型可以相互转换,一般非常量可以转化成常量,而反过来则不行.

2.3.4 constexpr和常量表达式.

常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式.用常量表达式初始化的const对象也是常量表达式.但是在实际中初始值有时候并非常量表达式,新标准规定可以将变量声明为constexpr以便编译器来验证是否是常量表达式. 6.5.2中可以看到,可以定义一种特殊的constexpr函数,这种函数足够简单可以在编译时刻就得捣计算的结果.
字面值类型:算数,引用,指针都属于字面值类型,自定义的类 IO库 string类型都不属于字面值类型. 但是对于指针,一个constexpr的指针初始值必须是nullptr或者0,或是存储于某个固定地址中的对象.
函数体内定义的变量不存放在固定地址,constexpr指针不能指向这样的变量,定义在所有函数体有固定的地址,函数体内也可以定义有固定地址的变量,constexpr引用能够绑定在这样的对象上,也可以初始化constexpr的指针.

指针和constexpr:
constexpr只对指针有效,而对所指对象无效,几个例子:
const int * p = nullptr; //指向常量的指针
constexpr int * p1 = nullptr;//指向整数的常量指针
p1和p的类型完全不一样,p是一个底层const,p1是一个顶层const.
constexpr const int * p2 = nullptr ;
p2是一个指向常量的常量指针.

2.5 处理类型;

为了避免类型的难写难记易出错.

2.5.1 类型别名

传统方法:typedef, 例如: typedef double wages;typedef wages base,*p; 不止可以用类型别名声明基本数据类型,也可以用基本数据类型构造复合的数据类型.新的语义: using SI = Sales_items.
指针常量和类型别名.
typedef char * pstring ;
const pstring ctr =0;
上面是一个指向char的常量指针,不要替换取理解,这是不对的. 替换的话成了指向常量的指针了.

2.5.2 auto 类型说明符

auto可以自动推断类型前提是必须有初始值
auto允许一次声明多个变量,但是一条声明语句中只能有一个基本数据类型.
复合类型,常量和auto:
编译器推断出的类型有可能和初始值的类型不完全一样,编译器会适当改变编译器类型更符合初始化的规则.使用引用其实是使用引用类型的对象.特别当引用作为初值时,编译器会将引用对象类型作为auto类型.
例如int i =0, r = &i; auto a = r; a是一个整型.
其次编译器会忽略顶层const,而保留底层const(针对引用和指针来说)
还可以将引用的类型设为auto,此时原来初始化规则仍然适用:const auto & i= 42;
但是当设置类型为auto引用时候,顶层const仍然保留.

2.5.3 delctype 类型指示符

有这种需求,希望从表达式推断出类型用来定义变量的类型而不是直接用表达式的值初始化变量,可以用decltype,其返回操作数的数据类型. 例如decltype(f()) sum =x;
这里编译器不实际调用函数,直接用返回类型推断.
decltype的类型是一个变量,则返回这个变量的类型(包括顶层const和引用在内),引用从来以所指对象的同义词出现,在这是个例外.

decltype和引用
有些表达式返回类型是个引用意味着其结果可以作为赋值语句的左值使用.
如果表达式结果是解引用操作,decltype得到的是引用.
如果decltype里面的变量名加括号,会被认为是一个表达式,返回引用的类型,如果没加括号,返回原数据类型.decltype((variable))返回引用,而decltype(variable)只有当variable自身是引用的时候才会返回引用.

2.6 自定义数据结构

数据结构是把一组相关的数据元素组织起来然后使用他们的策略的方法,库类型string iostream 等都以类的形式定义的.

2.6.1 定义Sales_data 类型

struct Sales_data{
std::string bookno;
unsigned units_sold = 0;
double revenue =0.0;
};
类数据成员:类内部定义类成员与定义普通的变量基本一样.可以为类内的数据成员提供一个初始值.没有定义初始值的成员也会被默认初始化.c++也可以用class来定义,唯一区别是class成员在默认情况下是private的.

2.6.2 略

2.6.3 编写自己的头文件

函数体内虽然可以定义类,但是一般不这么做.类一般定义在头文件中,而类所在头文件与类名一样.
头文件通常包含那些只能定义一次的变量,如类,const,constexpr变量.

预处理概述:
#include
#define
#ifndef
#ifdef
#endif

小结;
类型是c++的基础
类型规定了对象的存储要求及其所能执行的操作,c++语言提供了一套基础的内置类型,如int和char,这些类型与及其密切相关.类型分为常量和非常量,一个常量的对象必须初始化,而且有些一旦初始化后值就不能再改变.此外还可以定义复合的类型,如指针和引用,复合类型的定义以其他的类型为基础.
c++语言允许用户定义自己的类型,c++
本身也提供了一套高级抽象.

几道小题

1.int _ 这个声明合法么? 合法
2.下面程序合法么?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i;
std::cout << i << ” ” << sum << std::endl;
合法,作用域问题,内作用域可以声明同名变量覆盖外层作用域变量
3. 说明指针和引用区别; 请在文中查找
4.下列声明合法吗?
int i = 0;
(a) double* dp = &i; (b) int *ip = i; (c) int *p = &i;
(a)不合法,类型不一致,牵涉const才有合法转换(b)不合法(c) 合法.
5. 这个声明合法么?const int buf;不合法,要初始化

代码附录

#include <iostream>
int main()
{
    // i is an int; p is a pointer to int; r is a reference to int
    int i = 1024, *p = &i, &r = i;

    // three ways to print the value of i
    std::cout << i << " " <<  *p <<  " " << r << std::endl;

    int j = 42, *p2 = &j;
    int *&pref = p2;  // pref is a reference to the pointer p2

    // prints the value of j, which is the int to which p2 points
    std::cout << *pref << std::endl;

    // pref refers to a pointer; assigning &i to pref makes p point to i
    pref = &i; 
    std::cout << *pref << std::endl; // prints the value of i

    // dereferencing pref yields i, the int to which p2 points; 
    *pref = 0;  // changes i to 0

    std::cout << i << " " << *pref << std::endl;

    return 0;
}
#include <iostream>

// Program for illustration purposes only: It is bad style for a function
// to use a global variable and also define a local variable with the same name

int reused = 42;  // reused has global scope

int main()
{
    int unique = 0; // unique has block scope

    // output #1: uses global reused; prints 42 0
    std::cout << reused << " " << unique << std::endl;   

    int reused = 0; // new, local object named reused hides global reused

    // output #2: uses local reused; prints 0 0
    std::cout << reused << " " <<  unique << std::endl;  

    // output #3: explicitly requests the global reused; prints 42 0
    std::cout << ::reused << " " <<  unique << std::endl;  

    return 0;
}
#include <iostream>
using std::cout; using std::endl;

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;

    return 0;
}
#include <iostream>
int main() 
{
    std::cout << "Hello World!";  // simple character string literal
    std::cout << "";              // empty character string literal
    // literal using newlines and tabs
    std::cout << "\nCC\toptions\tfile.[cC]\n";

    // multiline string literal
    std::cout << "a really, really long string literal "
                 "that spans two lines" << std::endl;

    // three ways to print a capital M
    std::cout << 'M' << " " << '\115' << " " << '\x4d' << std::endl;

    unsigned long long bigVal = -1ULL;
    std::cout << bigVal << std::endl;

    return 0;
}
#include <iostream>

int main() {
    std::cout << '\n';       // prints a newline
    std::cout << "\tHi!\n";  // prints a tab followd by "Hi!" and a newline
    std::cout << "Hi \x4dO\115!\n"; // prints Hi MOM! followed by a newline
    std::cout << '\115' << '\n';    // prints M followed by a newline

    return 0;
}
#include <iostream>
int main()
{
    int i = 42;
    std::cout << i << std::endl; // prints 42
    if (i) // condition will evaluate as true
        i = 0;
    std::cout << i << std::endl; // prints 0

    bool b = 42;            // b is true
    std::cout << b << std::endl; // prints 1

    int j = b;              // j has value 1
    std::cout << j << std::endl; // prints 1

    double pi = 3.14;       // pi has value 3.14
    std::cout << pi << std::endl; // prints 3.14

    j = pi;                 // j has value 3
    std::cout << j << std::endl; // prints 3

    unsigned char c = -1;   // assuming 8-bit chars, c has value 255
    i = c;  // the character with value 255 is an unprintable character
            // assigns value of c (i.e., 255) to an int
    std::cout << i << std::endl; // prints 255

    return 0;
}
#include <iostream>

int main()
{
    unsigned u = 10, u2 = 42;
    std::cout << u2 - u << std::endl;  
    std::cout << u - u2 << std::endl; 

    int i = 10, i2 = 42;
    std::cout << i2 - i << std::endl;
    std::cout << i - i2 << std::endl;

    u = 42;
    i = 10;
    std::cout << i - u << std::endl;
    std::cout << u - i << std::endl;

    u = 10;
    i = -42;
    std::cout << i + i << std::endl;  // prints -84
    std::cout << u + i << std::endl;  // if 32-bit ints, prints 4294967264

    i = 10;
    std::cout << "good" << std::endl;
    while (i >= 0) {
        std::cout << i << std::endl;
        --i;
    }

    for (int i = 10; i >= 0; --i)
        std::cout << i << std::endl;

    for (unsigned u = 0; u <= 10; ++u) 
        std::cout << u << std::endl;  // prints 0 . . . 10

/* NOTE: the condition in the following loop 
         will run indefinitely
    // WRONG: u can never be less than 0; the condition will always succeed
    for (unsigned u = 10; u >= 0; --u)
        std::cout << u << std::endl;
*/
    u = 11; // start the loop one past the first element we want to print
    while (u > 0) {
         --u;        // decrement first, so that the last iteration will print 0
        std::cout << u << std::endl;  
    }

    // be wary of comparing ints and unsigned
    u = 10;
    i = -42;
    if (i < u)               // false: i is converted to unsigned
        std::cout << i << std::endl;
    else
        std::cout << u << std::endl;   // prints 10

    u = 42; u2 = 10;
    std::cout << u - u2 << std::endl; // ok: result is 32
    std::cout << u2 - u << std::endl; // ok: but the result will wrap around
}
#include <iostream>
using std::cout;
using std::endl;

int main()
{
    int ival = 1024;
    int *pi = &ival;   // pi points to an int
    int **ppi = &pi;   // ppi points to a pointer to an int
    cout << "The value of ival\n"
         << "direct value: " << ival << "\n"
         << "indirect value: " << *pi << "\n"
         << "doubly indirect value: " << **ppi
         << endl;

    int i = 2; 
    int *p1 = &i;     // p1 points to i
    *p1 = *p1 * *p1;  // equivalent to i = i * i
    cout << "i  = " << i << endl;

    *p1 *= *p1;       // equivalent to i *= i
    cout << "i  = " << i << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值