第2章 类型、常量及变量

2.1 C++单词

关键字就是保留字,预定义类型如 int 等也被当作保留字。

C++ 使用 Unicode 中的字符来构造单词

ASCII ∈ Unicode,ASCII使用最高位为0的八位二进制表示,可对128个字符编码。

汉字字符数量大,无法放入ASCII而只能放入Unicode。

char16_t表示双字节字符类型,支持UTF-16。

char32_t表示四字节字符类型,支持UTF-32。

        nullptr 类型

        nullptr 类型为std::nullptr_t 表示空指针常量

        对于实例数据成员指针如 int A::*q = nullptr 

        则  printf("%p", q);输出0 而printf("%d", q) 输出-1;

        对于其他类型指针如int *q = nullptr;printf("%d", p);输出0。

2.2 预定义类型

        注意 long int 等价于 long;

        用以说明存储位置特性

        auto、register、static、extern

        static

        static定义的静态变量和extern说明的函数外部变量编译后在数据段分配内存,使用extern说明的变量要么来自程序全局变量,要么来自函数外部定义的单元变量或static变量。作为程序一部分的数据段会随程序存放于磁盘文件中,未初始化的变量一般默认为0。C语言的函数参数和auto说明的局部变量在栈段分配内存,编译程序会自动分配和回收栈段的内存。

        register        

        register用于定义寄存器变量,当数量有限的寄存器分配完,就可以使用栈内存来代替寄存器。若寄存器变量初出现了取地址的操作,就会用栈内存来存储;如果有多余的寄存器没被使用且未取变量地址,即时局部变量定义为要分配栈内存的auto,也可能被编译优化成使用register存储。

        用以说明存储可变性

        const、constexpr、volatile、mutable 其中 mutable 用于说明实例函数数据成员的存储可变性。

        自动类型转换路径

        char→unsigned char→ short→unsigned short→ int→unsigned int→long→unsigned long→float→double→long double。

        一些细节

        输出:float, double:%e科学计数。%g自动选宽度小的e或f。

        字符串常量的类型:指向只读字符的指针,即const char *

        strlen("abc") = 3,但要4个字节存储,最后存储字符‘\0’,表示串结束。

        所有浮点数运算都会转化成double来计算,即使是两个float。

        char,signed char,unsigned char 是三种不同类型。

        常量表达式是编译时就能确定值的数值表达式。

   sizeof在编译时就能确定。

   sizeof3种形式:sizeof(类型表达式)、sizeof 数值表达式、sizeof...(类型参数包)

2.3相关常量

        与C差不多

2.4变量

        一些概念

                变量说明:描述变量的类型及名称,但没有初始化。可以说明多次。

                变量定义:描述变量的类型及名称,同时进行初始化。只能定义一次。

                说明实例:extern int x; extern int x; //变量可以说明多次

                定义实例:int x=3; extern int y=4; int z; //全局变量z的初始值为0

                模块静态变量:使用static在函数外部定义的变量。可通过单目 :: 访问。

                局部静态变量:使用static在函数内部定义的变量。

static int x, y; //模块静态变量x、y定义,默认初始值均为0 
    int main( ) { 
        static int y; //局部静态变量y定义, 初始值y=0 
        return ::y+x+y;//分别访问模块静态变量y,模块静态变量x,局部静态变量 
}

只读变量

        使用 const 或 constexpr 说明或定义的变量,定义时必须同时初始化。当前程序只能读不能修改其值。 constexpr 变量必须用常量表达式初始化,编译将出现该变量的地方优化为常量。

易变变量

        使用 volatile 说明或定义的变量,可以后初始化。当前程序没有修改其值,但是变量的值变了。不排除其它程序修改。

//const 实例:

  extern const int x;

  const int x=3; //定义必须显式初始化x

   //volatile 例:

  extern const int y;

  volatile int y;

    //可不显式初始化y,全局y=0 若y=0,语句if(y==2)是有意义的,因为易变变量y可以变为任何值。

在多任务环境下,定义 const volatile int z=0 是有意义的,不排除其它程序修改z使其值易变。

保留字 inline

用于定义:函数返回值。

inline函数外部变量的作用域和inline函数外部静态变量一样,都是局限于当前代码文件的,相当于默认加了static。

用 inline 定义的内容,不能被优化。

关于指针的常考点:

可不可以修改
const int x = 3; //不可修改x的值 
const int *y = &x; //可以修改y的值,但是y指向的const int实体不可修改 
const int *const z = &x; //不可修改z的值,且z指向的const int实体也不可改

所指单元值只读的指针(地址)不能赋给所指单元值可写的指针变量。

例如:

const int x = 3;   //等价int const x = 3;
const int *y = &x; //对 
int *z = y; //错:y是所指单元值只读的指针 
z = &x; //错:&x是是所指单元值只读的地址

证明:

(1)假设int *z=&x 正确(应用反证法证明)

(2)由于int ***z表示z指向的单元可写,故***z=5是正确的

(3)而*z修改的实际是变量x的值,const int x规定x是不可写的,矛盾。

所指单元值可写的指针(地址)赋给所指单元值只读的指针变量: y=z;

所指单元值易变的指针(地址)不能赋给所指单元值可写的指针变量,反之成立。即:将前例的const换成volatile或者const volatile,结论一样。

VS2019在X86编译模式下,使用4个字节表示地址

类型表达式的解释

        在一个类型表达式中,先解释优先级高,若优先级相同,则按结合性解释。

例如:

        int *y[10][20];

分析:在y的左边是*,右边是[10],[ ]优先级更高。

解释:

        (1) y是一个10元素数组;

        (2)每个数组元素均为20元素数组

        (3) 20个元素中的每个元素均为指针;

      (4)每个指针都指向一个整数

括号()可提高运算符的优先级,如:int (*z)[10][20]; (…)、[10]、[20]的运算符优先级相同,按照结合性,应依次从左向右解释。 其第(1)个解释应为z是一个指针,注意z与y的第(1)个解释的不同。

指针移动

        y[m][n]+1移动到下一整数,z+1移动到下一10*20整数数组。

改错题
const int x;   x = 1;      //?
const int* p;  p = &y;     //? 
int* const q1 = &x;        //? 
int* const q1 = &y;        //? 
int* const q1; q1 = &y;    //?
const int* const q2 = 0;   //?
const int* const q2 = &x;  //?
const int* const q2 = &y;  //?

volatile int a = 0;
while (a == 0);
volatile const int  b1 = 0;
volatile int const  b2 = 0;
const volatile int  b1 = 0;
const int volatile  b2 = 0;
int const volatile  b1 = 0;
int volatile const  b2 = 0;
int b = 0;
volatile int* p1 = &b;  //?
int* p2 = &a;           //?
//p1指向1个volatile int对象(可能会被其他线程改变),但其他线程不可能改变这个对象b。
//p2指向1个int对象(不会被其他线程改变),但实际上这个对象可能随时会被其他线程改变。

 答案:×✔×✔×✔✔✔✔;✔×

void指针

        void *p表示p指向的存储单元的字节数可以是任何大于0的整数。因此,任何类型的存储单元的地址(指针)都可以赋值给p。

int x=3;
p=&x;
double y=4;
p=&y;

        同理可知:delete <操作数>; //该操作数一定是void *类型,因为它可接受任何类型的指针或地址。

        但是:在向p所指的存储单元赋值时,不能修改任意个字节数的值:即 “*p=值” 是错误的。必须明确指出所修改的存储单元的类型——使用强制类型转换。例如,

*(int *)p=5; *(double *)p=3.2; //(int *)将p转换为指向4字节整型单元

引用:

引用是变量的别名,可以用来修饰变量、函数参数、函数返回值。

引用变量包括

        对左值的引用:

           [const] 类型名 &变量名 = 左值表达式;

        对右值的引用:

           [const] 类型名 &&变量名 = 纯右值表达式;

           const 类型名 &变量名 = 右值表达式;

引用变量初始化

        引用变量只能在定义时初始化1次,初始化之后对引用变量的赋值运算是对该引用变量所代表的存贮单元进行赋值。

        用左值初始化的引用变量:被引用的对象(内存单元)的别名(引用变量本身不占用内存)。

        用右值初始化的引用变量:编译器为被引用的对象申请一个缓存,该引用变量就是这个缓存的别名(引用变量本身不占用内存)。

        理解引用变量

                逻辑上:变量的别名(不占用存储单元)

                实现上:被编译为指针(占用存储单元)

右值引用变量 && 是C++11增加的新特性,主要用于移动语义(将1个对象内的资源迁移到另外1个对象,如移动构造、移动赋值等)。用 && 表示移动语义是一种编程约定(不是语法)。

例如,void copy(A &&a, A &b) 一般表示移动拷贝,当类A中含有内部缓冲区时,则将对象a的缓冲区移动到对象b中去(对象b内部不需申请新的缓冲区)。

                实例理解 

1. 

int  x = 1;   int* p = &x;
const int  y = 2;              //注意:y本质上是一个左值
int& q = x;                    //q是x的别名, 可以通过q重新赋值
int*& q = p;                   //q是指针p的别名, 可以通过q修改p所指的内容
const int& q = x;              //q是x的别名, 但不能通过q修改x的值
const int& q = y;              //q是y的别名, 不能通过q重新赋值
const int& q = 0;              //申请1个存贮单元, 将0保存到该存贮单元, 
//q是这个存贮单元的别名, 不能重新赋值
int&& q = (int)x;           //申请1个存贮单元, 将x的值拷贝到该存贮单元, 
//q是这个存贮单元的别名, q能重新赋值 (但不会改变x)
const int&& q = y;              //错:  y本质上是一个左值
const int&& q = x++;           //申请1个存贮单元, 将x的值拷贝到该存贮单元

 2. 

int x = 3;
const int y = 4;
volatile int z = 5;

int& u = x;                      //对,可以 u = 4
int& u = y;                    //错,如果对的话则可以通过u可以改变y
const int& u = x;            //对,不能通过u改变x
const int& u = y;            //对,不能通过u改变y
long& u = x;                  //错,等价 long &u = (long)x,而 (long)x 是右值
int&& u = x;                  //错,只能用右值去初始化
int&& u = x--;              //对,x--是右值
int&& u = --x;              //错,--x是左值
int&& u = y;                //错,y本质上是左值
long&& u = y;                //对,等价 long &&u = (long)y,而 (long)y 是右值
int&& u = 3;                //对,u可以重新赋值,如 u = 1
const int&& u = 3;          //对,但u不能重新赋值
volatile int& u = x;        //对,见前几页ppt中的 volatile int *p1 = &b;
int& u = z;                 //错,见前几页ppt中的 int *p2 = &a;
volatile int& u = y;        //错:通过u可改变y,但 y 是const

3. 

int& &u;                    //错:引用变量u去引用无内存的引用,int &&u; ?
int&* v;                    //错:指针v不能指向引用变量 (无内存)
int* p;  int*& v = p;       //对:v是指针p的引用,可以使用*v, v[1]等
int x = 3;
int& y = x;                 //对:y共享x的内存,y=5将使x=5,x=6将使y=6
int& z = y;                 //对:z引用y所引用的变量x,注意z、y同类型。非z引用y。
int&& *p;                   //错:p不能指向没有内存的引用
int&& &q;                   //错:int &&没有内存,不能被q引用
int& &&r;                   //错:int &没有内存,不能被r引用。
int&& &&s;                  //错:int &&没有内存,不能被s引用
int&& t[4];                 //错:数组的元素不能为int &&:数组内存空间为0。
int& s[3];                  //错:数组应当有内存,而这里数组元素是无内存的引用
int a[3]; int(&t)[3] = a;   //对:数组有内存,t是数组a的别名
const int a[3] = { 1,2,3 };   int(&& t)[3] = a;   //错:a本质上是左值。 
int(&& u)[3] = { 1,2,3 };      //对:{1, 2, 3} 是右值

4. 

//若函数返回的不是左值引用,则一定是右值;
int&& x = printf(“abcdefg”);  //对:printf( )返回无址右值
int&& a = 2;          //对:引用无址右值
int&& b = a;          //错:a是有名有址的(a是1个缓存的别名)
int&& f() { return 2; }
int&& c = f();           //对:f返回的是无址引用,是无址的

//位段成员是无址(右值)的
int&& x = printf(“abcdefg”);  //对:printf()返回1个右值
struct A {
    int a;                /*普通成员:有址*/
    int b : 3;            /*位段成员:无址*/
} p = { 1, 2 };
int&& q = p.a;        //错
int&& r = p.b;        //对:位段只能是右值

        问题:const int x = 1 是左值还是右值?

        本质上是左值,因为x占有存贮单元。 通过特殊方法可以修改x的值: *(int *)&x = 0;

enum枚举:

        一般被编译为整型,而枚举元素有相应的整型常量值

        第一个枚举元素的值默认为0,后一个元素的值默认在前一个的值加1

enum WEEKDAY { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; 
WEEKDAY w1 = Sun, w2(Mon); //可用限定名WEEKDAY::Sun,Sun=0, mon=1

        也可以为枚举元素指定值,哪怕是重复的整数值。

        如果使用"enum class"或者"enum struct"定义枚举类型,则其元素必须使用类型名限定限定元素名

enum struct RND { e = 2, f = 0, g, h }; //正确:e=2,f=0,g=1,h= 2 
RND m = RND::h; //必须用限定名RND::h 
int n = sizeof(RND::h); //n=4, 枚举元素实现为整数

数组存储:

        数组元素按行存储, 对于“int a[2][3] = { {1,2,3}, {4,5,6} }; ”,先存第1行再存第2行

        a : 1, 2, 3, 4, 5, 6 //第1个元素为a[0][0], 第2个为a[0][1],第4个为a[1][0]

        若上述a为全局变量,则a在数据段分配内存,1, 2…6等初始值存放于该内存。

        若上述a为静态变量,则a的内存分配及初始化值存放情况同上。

        若上述a为函数内定义的局部非静态变量,则a的内存在栈段分配,而初始化值则在数据段分配,最终函数使用栈段的内存。

        C++数组并不存放每维的长度信息,因此也没有办法自动实现下标越界判断。每维下标的起始值默认为0。

        数组名a代表数组的首地址,其代表的类型为 int[2][3] 或 int(*)[3]。

结构与联合:

  •         结构是使用struct定义的一组数据成员,每个成员都要分配相应的内存。
  •         数据成员可以是基本数据类型如char、int等。
  •         数据成员也可以是复杂的结构或联合成员。
  •         所有成员都可被任意函数访问。伏笔
  •         类型不大于int的成员可以定义为使用若干位二进制的位段类型。
  •         联合是使用union定义的一组数据成员,所有成员共用最大成员分配的内存。
  •         数据成员可以是基本数据类型、结构或联合类型
  •         所有成员都可被任意函数访问
  •         类型不大于int的成员可以定义为使用若干位二进制的位段类型
  •         结构和联合还可以包含函数成员。(伏笔

你可能感兴趣的:(C++从入门到入门,开发语言,c++)