菜鸟的C++ 知识盲区(跌倒)到知识发现(爬起)---------第二章 变量和基本类型

(禁止转载   (虽然我写的不好。。。。))

前言

说来话长,本人是一个不合格的程序员,最起码我觉得我水平很菜。本科就读于北方一个没落的211,学的是机械设计制造及其自动化,基本上本科没有接触过什么“高深”的关于编程的项目,不过稀里糊涂计算机二级考过了,但是C语言并没有学的很好,什么指针啦只是大概知道。本科阶段唯一让我欣慰的就是踩了狗屎运保研到南京的一所985,然后选择了机械电子专业,在这里特别感谢该学校的tmq老师,然后从此读研期间我便转向了嵌入式。传统的就业不是很好工资待遇不是很高,但是我本科实在太菜。研究生三年我自我感觉还是学了点东西的,最起码C语言熟悉了,只是熟悉,熟悉,熟悉。然后也试着学习C++,发现C++是一门从入门到放弃的语言。放弃了然后在入门,就这样。白驹过隙,岁月如梭,我现在已经踏上了工作岗位,来到杭州一个公司,做嵌入式开发的相关工作。说实话,我现在很菜比,对,年龄也大了,9x年的,有时候很绝望,因为我是一个随时被淘汰的程序猿。哎~!………………话说我在学习C++途中,遇到不会的经常csdn,看一些博客,但是并没有发现系统的一个深入浅出的对C++讲解的(也许有,我未发现)。于是,我准备自己写一下,一来自己巩固知识,毕竟脑子不好,记忆力衰退。二来帮助那些和我遇到同样困惑的学习C++的人。

关于C++学习,我会连载,时间不定,内容基本摘自C++ primer这本书(中文版,我英语菜),主要涵盖对于我这个菜逼来说的比较混淆的容易忘记的知识点和概念。姑且把我的这系列博客就叫  菜鸟的C++ 知识盲区到知识黑洞!(注意,本博客只写本菜鸟的知识盲区~~~)

言归正传~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                                              2.变量和基本类型

2.1基本内置类型

C++中将内置类型分为算数类型和空类型。

bool b = 42; // b is true
int i = b; // i has value 1
i = 3.14; // i has value 3
double pi = i; // pi has value 3.0
unsigned char c = -1; // assuming 8-bit chars, c has value 255
signed char c2 = 256; // assuming 8-bit chars, the value of c2 is undefined
  1.  第一个布尔值是非0,所以b是true的,但是它给int  i 赋值,结果却是1 。这个是bool与int转换。
  2. int 给double赋值,结果只保留浮点数之前的小数部分。
  3. 无符号的数字赋值超出范围时,结果是初始值对无符号类型表示数值总数取模后的余数。(说实话这句话我理解的不够深刻。具体参照 https://www.cnblogs.com/houqi/p/5644384.html

当一个算数表达式中既有无符号数又有int的时候,那个int值会转变成无符号的数字。

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

上述代码第二个表达式中写的很清楚了,会先把-42转换成无符号然后再运算,怪不得我司规范中强调不同类型的最好不要在防灾一起运算。

20 /* 十进制*/ 024 /* 八进制*/ 0x14 /* 十六进制

默认情况下十进制字面值常量是带符号的,八进制和十六进制可能带符号也坑内不带符号。

指定字面值类型:

L'a' // wide character literal, type is   wchar_t
u8"hi!" // utf-8 string literal   (utf-8 encodes a Unicode character in 8 bits)
42ULL // unsigned integer literal, type is unsigned long long
1E-3F // single-precision floating-point literal, type is float
3.14159L // extended-precision floating-point literal, type is long double

 (原谅我不想画表格)

上述表格可以指定指定字面值类型。(当使用一个场整型字面值的时候,请使用大写字母L 小写l和1太像了

2.2变量

变量的定义的基本单形式:

                                   类型说明符+变量名组成的列表; 记住类型说明符这个词语

waring:初始化变量和赋值有本质区别。

C++11新标准的一部分,用花括号初始化变量得到了广泛的应用。

int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);

但是用于内置类型的变量时,这种初始化形式有个重要的特点:如果我们使用列表初始化,而且初始值存在丢失信息的风险,比如说值的精度等,这个时候编译器会报错的:

long  double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated

当然,定义变量的时候没有指定初始值,则变量会被默认初始化。(此时我联系到默认构造函数)而默认值由变量的类型以及定义变量的位置决定。

声明和定义是有本质区别的,变量只能被定义一次,但是可以被声明很多次。

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

C++标志符必须以字母和下划线开头,我第一次就傻乎乎的以数字开头哎~

接下来讲讲作用域:嵌套的作用域是指被包含的作用域被称为内层作用域,包含着别的作用域被称为外层作用域,注意:允许外层内层作用域重新定义外层作用域已经有的名字。这句话就是说局部变量和全局变量可以同名,但是呢局部变量会覆盖全局变量。之前我觉得不可以同名,但是这个我真是盲区了。所以我司规范中强调过这个规范~~~~~~~~~~~~~~~~~~~~~~

2.3复合类型

复合类型很有意思,也是难点。特别是这个C中传说的指针,有人说过C把指针学会了其他都学会了。在本章中着重介绍两种复合类型,引用和指针。其中引用是C中没有的。

复合类型的定义是由一个数据类型和紧随其后的一个声明符列表组成,每个声明符命名了一个变量并指定该变量与基本数据类型的关系。一般情况下声明符就是变量名,其实还有更复杂的声明符。

先说引用:

int ival = 1024;
int &refVal = ival; // refVal refers to (is another name for) ival
int &refVal2; // error: a reference must be initialized

第三个为什么会出错呢,原因很简单,就是引用在定义的时候必须初始化。通俗的讲,引用就是创建的对象的别名。

指针和引用最大的不同就是指针无须在定义的时候初始化,如果指针不在定义的初始化,它的指向是不确定的。

我觉得上表应该让像我一样菜鸟好好看看,我之前一直是把  int* 结合一起写的,其实是错误的,或者说误导人的~就是因为没有理解好符合类型的声明:

int* p; // legal but might be misleading  注意*和int紧紧挨在一起 和p中间有一个空格。合法但是误人子弟

注意*和int紧紧挨在一起 和p中间有一个空格。合法但是误人子弟。很多人包括我之前以为基本数据类型是int* ,对是int*!!!!这样是错误的想法!即使这样定义指针,基本数据类型还是int而非int*,*只是修饰了p而已。接着看下面定义多个变量:

int* p1, p2; // p1 is a pointer to int; p2 is an int

p1是指向int的指针,p2是int。如果把int和* 看成一个整体的话,按照逻辑来讲 p1和p2都是指向int的指针了!!!所以说,涉及到指针和引用的声明,一般有两种写法:第一种是修饰符和变量名放在一起,这种着重强调变量具有符合类型

int *p1, *p2; // both p1 and p2 are pointers to int

第二种是把修饰符和变量名放在一起,着重强调本次申明定义了一种符合类型:

int* p1; // p1 is a pointer to int
int* p2; // p2 is a pointer to int

我们的神书是采用第一种写法,我了不要误人子弟~我强烈建议是用第一种写法~~~~~~~~~~~~~

空指针不指向任何对象~

2.4const  限定符

2.4.1const的引用:

const  单词翻译过来的意思就是常量的意思。所以在C++ 中我们希望他修饰的变量值是不变的。

注意 const 对象被创建后,其值是不能改变的。更重要的是:const 对象必须初始化~!~~~

const int i = get_size(); // ok: initialized at run time
const int j = 42; // ok: initialized at compile time
const int k; // error: k is uninitialized const

上面第三个就是因为没有初始化~~。

 

对常量的引用就是把引用绑定在const对象上,与普通引用不同的是,对常量的引用不能被修改它所绑定的对象~。这句话有点绕口,请看下面的程序代码的例子:

const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are co
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object

注意上面第四个:试图用一个非常量的引用指向一个常量对象。假如可以,那么这个非常量的引用改变值,那岂不是前后自相矛盾吗?

对const的引用可能引用一个并非const的对象:

int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const

不允许通过r2修改i的值~。 但是,i的值可以通过其他方式修改~~~,好好理解该话。

2.4.2 const和指针:

指向常量的指针不能用于改变其所指的对象的值,要想存放常量对象的地址,只能使用指向常量的指针。这句话有点绕~请看下面的例子:

const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = &pi; // error: ptr is a plain pointer
const double *cptr = &pi; // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr

注意:和常量引用一样,指向常量的指针也没有规定所指的对象必须是个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值。

记住指针是对象!!而引用不是对象,所以初始化的时候指针可以不用赋值,而引用必须赋值。另外,可以允许把指针本身定位常量。指针一旦定位常量,就必须初始化了:

int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = &pi; // pip is a const pointer to a const

要想弄清楚这些这些声明的含义最简单有效的办法就是从右向左阅读,离着curRrr最近的符号是const,所以它是一个常量对象,对象的类型由声明符号其余部分确定。声明符是*,所以它是一个常量指针。

2.4.3 顶层const

关于这个顶层只不过是一个概念,有顶层所以肯定有底层。顶层表示指针本省是一个常量,底层表示指针所指的对象是一个常量。所以,顶层const可以表示任意的对象是常量,这一点对任何数据都适用,底层的const则 与指针和引用等符合类型的基本类型部分有关系。如下:

int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level

当执行对象的拷贝操作常量低顶层const和底层const有明显区别,其中顶层不会受到什么影响,底层const会受到一些限制。

注意下面这句话:拷入和拷出的对象必须具备相同的底层const资格,或者两个对象的数据类型能够相互转换。一般来说,非常量可以转换成常量,反之不行!

int *p = p3; // error: p3 has a low-level const but p doesn't
p2 = p3; // ok: p2 has the same low-level const qualification as p3
p2 = &i; // ok: we can convert int* to const int*
int &r = ci; // error: can't bind an ordinary int& to a const int object
const int &r2 = i; // ok: can bind const int& to plain int

2.4.4 constexpr和常量表达式

常量表达式是指值不会改变并且在编译的过程中就能得到计算结果的表达式:

const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression

C++ 11新标准规定,允许将变量声明为constexpr类型以便编译器来验证变量的值是否一个常量表达式。

必须明确一点,在constexpr声明中定义了一个指针,限定符constexper仅仅对指针有效,与指针所指的对象无关:

const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int

2.5处理类型

2.5.1类型别名

有两种方法可以定义类型别名,传统的方法是使用关键字 typedef :

typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*

新标准规定了一种新的方法使用别名声明来定义类型的别名:

using SI = Sales_item; // SI is a synonym for Sales_item

这种方法用关键字using 作为别名声明的开始,其后紧跟着别名和等号。

如果某个类型别名指代的是复合类型或常量,那么把它用到申明语句里就会产生意想不到的后果。例如:

typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char

上面的语句声明了pstring ,它实际上是类型char*的别名。const是给定类型的修饰,pstring实际上是指向char的指针,因此,const pstring 就是指向char的常量指针,而非指向常量字符的指针。遇到一条使用了类型别名的声明语句的时候,人们往往会错误的尝试把类型别名替换成本来的样子,以理解该含义:

const char *cstr = 0; // wrong interpretation of const pstring cstr

记住上面的理解是错误的~是对const pstring cstr的错误理解啊~~~~~~~~~~

2.5.2auto类型说明符

C++11 新标准引入了auto类型说明符,用它能让编译器代替我们去分析表达式所属的类型。编译器推断出来的auto类型有时候和初始值类型不一样,编译器会适当地改变结果类型使其符合初始化的规则。auto一般会忽略掉顶层const,同时底层const会保留下来。例如:

const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)

如果希望推断出auto是顶层cosnt,需要明确指出:

const auto f =ci

2.5.3 decltyoe

C++11新标准引入第二种类型说明符 decltype ,它的作用是选择并返回操作数的数据类型,编译器会分析表达式并得到数据类型:

decltype(f()) sum = x; // sum has whatever type f returns

decltype 处理顶层const和引用的方式与auto并不相同。如果decltype使用的表达式是一个变量,则decltype返回改变量的类型

(包括顶层const和引用在内)

const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized

 decltype和引用

int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized

这个有点复杂,我觉的算黑洞的边缘了,也不知道C++11怎么搞得,慢慢来~

r是一个引用,因此decltype(r)的结果是引用类型。如果想让结果类型r所指的类型,可以把r作为表达式的一部分,r+0,所以第二行就变长了int  。另一方面,解引用指针可以得到指针所指的对象,而且还能给这个对象赋值,因此,decltype(*p)的结果就是int& 而非int。(我想说我理解的是int*)。

另外decltype和auto的另一处重要区别是,decltype的结果类型和表达式形式密切相关。有一种情况特别注意:

对于decltype所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果是该变量的类型。如果给变量多加了一层或多层括号,编译器会把它认为是一个表达式。变量是一种可以作为赋值语句的左值的特殊的表达式,所有这样的decltype就会得到一个引用类型:

decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int

切记,decltype((variable))(注意是双括号)的结果永远是引用。

/**************************************************ending****************************************************************/

这一章的内容的知识盲点就到这里~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

以后有的再添加吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值