变量及关键字

第2章、变量和基本类型

2.1、基本内置

包括算术类型+空类型(void)

2.1.1算术类型

分类:整型(字符、整型数、布尔变量),浮点型

bool布尔未定义
char字符8位
wchar_t宽字节16位
short短整型16位
int整型16位
long长整型32位
float单精度浮点型 
double双精度浮点型 

一个char大小和一个机器字节一样

C++规定:一个int至少和一个short一样,一个long至少和一个int

float通常1个字(32比特),double通常2个字(64比特)

带符号类型和无符号类型

8比特的signed char:-128~127(-2^7~2^7-1);unsigned char:0~255(0~2^8-1)

2.1.2类型转换

1、将一个负数赋给一个无符号类型,结果是初始值对无符号类型表示的总数取模后的余数

eg:unsigned char c = -1; c为255;

  • 计算机角度

计算机中二进制数是以补码保存的,-1原码是10000001,反码11111110,补码11111111,看做有符号类型,就是255

若111是补码,先-1,得到反码110,再取反,101

规则:

原码到反码:符号位不变,数值位“按位取反”;

反码到补码:+1

  • 数学角度
负数取模问题

1、整数除法取整

向上取整(-9/4 = -2)ceiling

向下取整(-9/4 = -3)floor

向零取整(-9/4 = -2)Truncate,舍去小数部分

2、取模

r = a - (a / b) x b;

eg:(-1)%256 = -1 - ((-1) / 256) *256 = 255;

当表达式中既有带符号类型又有无符号类型, 当带符号类型取值为负时会出现异常,因为带符号数会自动转换成无符号数

2.1.3字面值常量

整型和浮点型字面值

十进制:20; 八进制:024   十六进制:0x14

字符和字符串字面值

char字面值常量:单括号

字符串字面值常量:双括号,实际上是由常量字符组成的数组,编译器在字符串的结尾处添加一个空字符(‘\0’)

'a'一个字符;"A":字符的数组,两个字符

2.2 变量

2.2.1 变量定义

初始化方式:

拷贝初始化:使用=初始化一个变量,编译器把等号右侧的初始值拷贝到新创建的对象
直接初始化:不使用=
列表初始化:新标准中,对序列,用{}
string s5 = "hiya";  //拷贝初始化

string s6("hiya");   //直接初始化

 

默认初始化:

  • 如果定义对象时没有指定初始值,则变量被默认初始化
  • 初始化的方式:由变量类型和变量位置决定

(1)内置类型变量如果没有被显式初始化,值由定义的位置决定。定义于任何函数体之外的变量被初始化为0;定义在函数体内部的内置变量不被初始化,一个不被初始化的内置类型变量的值是未定义的,如果试图拷贝或者访问将引发错误。

(2)绝大多数类支持无需显式初始化而定义对象,类提供了一个合适的默认值(eg:string默认值为空串“”)

 

值初始化:

  • 如果不支持默认初始化,则必须提供初始值

 

列表初始化:

用花括号来初始化变量:(如下都是)

	int a = { 0 };
	int b{ 0 };

在使用内置类型变量时,初始化有一特点:如果使用列表初始化且初始值存在丢失信息的风险时,编译器将报错;

long double ld = 3.1415926;
int a{ ld }, b = { ld };

 

2.2.2 变量声明和定义的关系

分离式编译,将声明与定义分开

声明(declaration):使得名字为程序所知

定义(definition):负责创建与名字关联的实体;

相同点:都规定了变量的类型和名字(声明一个变量而非定义:关键字extern)

不同点:定义还申请了存储空间,还可能为变量赋一个初始值(任何包含了显式初始化的声明即成为定义)

 

在函数内部,如果试图初始化一个extern标记的变量,将引发错误(防止两次初始化

 

变量能且仅能被定义一次,但是可以多次声明

2.2.3 标识符

2.2.4 名字的作用域

//声明与定义
extern int i; //声明而非定义
int j; //声明并定义

 

2.3 复合类型(参考指针与引用)

2.4 const限定符

const对象创建之后其值不能再改变,因此const对象必须初始化

 

const和初始化

在不改变const对象的操作中包括初始化,用一个对象初始化另外一个对象,是不是const无关紧要;

一旦初始化完成,两个对象就没什么关系

2.4.1 const的引用

不能一个非常量引用指向一个常量对象,如果该初始化合法,则可以通过非常量引用改变常量对象的值,不正确

初始化和对const的引用

1、初始化常量引用时允许任意表达式作为初始值(非常量对象、字面值),只要该表达式的结果能转换成引用的类型,如double转int即可。    程序中,ri绑定的是一个临时量对象(编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未知名对象)

double dval = 3.14;

cont int &ri = dval;

 

编译器进行的工作:

const int temp = dval;    //由双精度浮点型生成一个临时的整型常量

const int &ri = temp;      //由ri绑定这个临时量

当ri不是常量时,绑定的是一个临时量,还是不能通过ri改变dval的值,无意义,C++也把这种行为归为非法行为。

 

这就是为啥

可以用常量的引用绑定一个非常量(其实绑定的是一个临时量)

不能用非常量的引用绑定一个常量(无意义,定为非法)

指针和const(与引用类似)

1、指向常量的指针不能用于改变其所指的对象

2、存放常量对象的地址,只能使用指向常量的指针

3、允许另一个指向常量的指针指向一个非常量对象

(使用常量的指针仅仅要求不能通过该指针改变对象的值,并没有规定那个对象的值不能通过其他途径改变)

double dval = 3.14;                 //一个双精度浮点型,其值可以改变

const double *cptr = &dval;    //正确:但是不能通过cptr改变dval的值

 

2.4.4 constexpr和常量表达式

定义:值不会改变并且在编译过程就能得到计算结果的表达式,包括字面值(看数据类型和初始值)

const int sz = get_size(); //不是常量表达式

 

constexpr变量

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

字面值类型

包括算术类型,引用,指针

一个constexpr指针的初始值必须是nullptr或者0,或者是存储于固定地址中的对象

 

函数体内定义的变量一般来说并非存放于固定地址中,因此constexpr指针不能指向这样的对象;相反,定义于函数体之外的对象其地址固定不变,能用来初始化constexpr指针;

 

指针与constexpr

1、在constexpr中声明了一个指针,限定符只对指针有效,与指针所指对象无关

const int *p = nullptr;    //p是一个指向常量的指针

constexpr int *q = nullptr; //p是一个指向int的常量指针(constexpr把定义的对象置为了顶层const)

 

//常量创建之后必须初始化
const int i = get_size(); //不是常量表达式
const int j;  //错误


//const与初始化
int i = 42;
const ci = i; //正确
int j = ci; //正确

顶层const:

  • 顶层const表示指针本身是一个常量,底层const表示指针所指对象是一个常量,更一般的,顶层const可以表示任意的对象是常量,底层const则与指针和引用等复合类型的基本类型有关;指针类型即可以是顶层const,也可以是底层const
int i = 0;
int *const p1 = &i; //不能改变p1的值,顶层const
const int ci = 42;  //不能改变ci的值,顶层const
const int *p2 = &ci;//允许改变p2的值,底层const
const int *const p3 = p2; //靠右的是顶层const,靠左的是底层const
const int &r = ci;  //用于声明引用的都是底层const
  •  拷贝时候的区别:顶层const不受影响,执行拷贝操作不会改变被拷贝对象的值;底层const受影响,拷入与拷出的对象必须具有相同的底层const,或者两个对象的数据类型可以转化(一般,非常量可以转化为常量,反之不行)
int *p = p3; //错误:p3包含底层const的定义,而p没有
p2 = p3;     //正确:p2和p3都是底层const
p2 = &i;     //正确:int* 可以转化为const int*
int &r = ci; //错误:普通的int&不能绑定到int常量上
const int &r2 = i; //正确:const int&可以绑定到普通int上

 2.5 处理类型

2.5.1 类型别名

1、关键字typedef

2、关键字using(C++11)

typedef double *p;   //p是double*的同义词

using SI = Sales_item;  //SI是类型别名

 

2.5.2 auto类型说明符(C++11)

auto让编译器通过初始值推算变量的类型,auto定义的变量必须有初始值

2.5.3 decltype类型指示符(C++11)

作用:选择并返回操作数的数据类型,编译器分析表达式并得到它的值,却不实际计算表达式的值

decltype (f()) sum = x;

处理顶层const:

1、auto一般会忽略顶层const,但是底层const会被留下来

2、如果decltype使用的表达式是一个变量,则dectlype返回该变量的类型,包括顶层const和引用

第三章、字符串、向量和数组

string:支持可变长字符串

vector:支持可变长集合

iterator;配套类型

3.1 命名空间的using声明

3.2 标准库类型string

string和vector

string:可变长字符序列

vector:某种给定类型对象的可变长序列

命名空间的using声明

1、作用域操作符::

意思是编译器从操作符左侧名字所示的作用域中寻找右侧那个名字

std::cin,使用命名空间中的cin

 

初始化string对象的方式

string s1;                 //默认初始化,为空串

string s2(s1);           //s2是s1的副本

string s2 = s1;         //等价于string s2(s1);

string s3("value");    //s3是字面值"value"的副本,字面值中除了最后那个空字符外其他所有字符都被拷贝到新创建的string中

string s3 = "value";   //等价string s3("value");

string s4(n, 'c');        //s4初始化为连续n个字符c组成的字符串

直接初始化与拷贝初始化

使用=号初始化变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去

不适用=号,使用的是直接初始化

 

string对象上的操作

读写string对象

string s;

cin >> s;    //执行读操作时,string对象会自动忽略开头的空白(即空格符、换行符、制表符等),并从第一个真正的字符开始读起,知道遇到下一次空白为止

 

string的empty和size操作

string::size_type:size()返回类型,无符号类型的值,可以存放下任何string对象的大小

 

!!!一个表达式中已经有了size()函数,就不要使用int,假设n是负值的int,s.size() < n 一般返回true,因为负数n自动转换为一个比较大的无符号数

字面值和string对象相加

1、C++中的字符串字面值并不是标准库类型string的对象,字符串字面值同string是不同类型

2、标准库类型允许把字符字面值和字符串字面值转换成string对象

3、当把string对象和字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string

string s1 = "hello";

string s4 = s1 + ',';   //正确

string s6 = s1 +"," + "world";  //正确,等价于string temp = s1 + ","; string s6 = temp + "world";

string s7 = "hello" + "," + s2;  //错误

 

范围for语句

C++11

for(auto c: str)

for(auto &c : str)

//读取未知数量的字符串
int main(){
    string word;
    while(cin >> word){
        cout << word << endl;
    }
    return 0;
}

//使用getline读取整一行
int main(){
    string line;
    while(getline(cin, line)){    //保留输入的空白符,从给定的输入流中读入内容,直到遇到换行符
        cout << line << endl;
    }
    return 0;
}

标准库类型vector 

vector

1、类模板(而非类型,vector<int>是类型)

编译器根据模板创建类或函数的过程称为实例化

 

2、引用不是对象,所以不存在包含引用的vector

定义和初始化vector对象

c++ primer p87

1、默认初始化

2、直接初始化

3、复制初始化

4、列表初始化(其实调用拷贝构造函数)

例外:

1、使用拷贝初始化时(使用=),只能提供一个初始值;

2、提供的是一个类内初始值,只能使用拷贝初始化或使用花括号形式的初始化

3、如果提供的是初始值列表,只能把初始值放在花括号里进行列表初始化,不能放在圆括号里

 

vector<string> v1 {"a", "an", "the"};  //列表初始化

vector<string> v1 ("a", "an", "the");  //错误

 

值初始化

向vector中添加元素

如果循环体内包含有向vector对象添加元素的语句,不能使用范围for

范围for语句等价的语句:

for (auto beg = v.begin(), end = v.end(); beg != end; ++beg) {

        .......

}

范围for语句中,预存了end()的值,一旦在序列中添加元素,end()的值可能变得无效

vector操作

vector.size():返回值是由vector定义的size_type类型

vector<int>::size_type //正确

vector::size_type          //错误

 

下标的类型是相应的size_type类型,vector[0]

 

迭代器介绍

使用迭代器

begin():返回指向第一个元素的迭代器

end():返回尾后迭代器

 

若容器为空,则begin()和end()返回同一迭代器,都是尾后迭代器

 

关于前缀++与后缀++

前缀++改变了对象的状态并返回对象改变后的状态,不需要创建临时对象。

后缀++改变了对象的状态但是返回的是对象改变前的状态,并且需要创建一个临时对象。

 

迭代器尽量使用前缀++

 

迭代器类型

vector<int>::iterator it;  //能读写

vector<int>::const_iterator it;  //能读,不能写

 

begin和end的返回类型由对象是否为常量决定

 

c++11中,cbegin和cend,不管vector本身是否为常量,返回值都是const_iterator

 

!!!但凡使用了迭代器的循环,不要向迭代器所属的容器添加元素,会使迭代器失效

结合解引用和成员访问的操作

(*it).empty()      //解引用,然后调用结果对象的empty成员

*it.empty           //错误,it是迭代器,没有empty成员

it->empty            //等价于(*it).empty(), 使用箭头运算符->

迭代器运算

iter1 - iter2   //得到两迭代器距离

//只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就可以相减,类型是difference_type的带符号整型数,可正可负

//依次处理字符直到遇到空白
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it){
    *it = toupper(*it);
}

3.5 数组

参考C和指针中数组部分https://blog.csdn.net/a1059682127/article/details/103692849

 第4章 表达式

重载运算符

运算符作用于类类型的运算对象,可以通过运算符重载重新定义该运算符的含义。使用运算符重载可以令程序易于编写和阅读

基本概念

1、重载的运算符是具有特殊名字的函数,名字由关键字operator和其后要定义的运算符号共同组成。重载运算符包括返回类型、参数列表和函数体

2、如果重载运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式地this指针上。

3、当作用符作用于内置类型的运算对象时,无法改变运算符的含义

int operator+(int, int); //错误:不能为int重定义运算符

4、对于一个重载运算符,其优先级和结合律与对应的内置运算符保持一致。 

 

左值和右值

C语言:左值可以位于赋值语句左侧,右值则不能

C++:

1、当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)

2、在需要右值的地方可以用左值来代替(实际使用的是其值),但是不能把右值当成左值使用

4.1.1 优先级与结合律

4.1.3 求值顺序

优先级:规定了运算对象的组成方式,eg:高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密结合在一起

4种运算符明确规定了运算对象的求值顺序:

1、&& 先求左侧运算对象的值,只有当左侧运算对象的值为真时才继续求右侧运算对象的值

2、||   先求左侧运算对象的值,只有当左侧运算对象的值为假时才继续求右侧运算对象的值(短路求值)

3、条件运算符?:把简单的if-else运算逻辑嵌入到单个表达式中:cond ? expr1 : expr2; //只对表达式expr1, expr2中的1个求值

4、,逗号运算符

4.2 算术运算符

优先级:一元运算符>乘除>加法减法

一元运算符:&, *(解引用), +expr(一元正号), -expr(一元负号)

乘除:*,/,  %

 

当一元正号作用于一个指针或算术值时,返回运算对象值的一个(提升后)的副本

4.3 逻辑和关系运算符

注意 if(i < j < k) //拿i < j 的布尔值结果跟K比较

正确方式:if(i < j && j< k)

4.4 赋值运算符

1、赋值运算符的左侧运算对象必须是一个可修改左值

int i = 0, j = 0, k = 0;

const int ci = i;

常见的临时对象:常量、表达式等(右值)

1024 = k;  //错误:字面值是右值

i + j = k;  //错误:算术表达式是右值

ci = k; //错误:ci是一个常量左值

赋值运算符优先级低于关系运算符的优先级

·

4.5 递增和递减运算符

前置版本:将运算对象+1,然后将改变后的对象作为求值结果

后置版本:将运算对象+1,返回改变之前的对象值的副本

除非必须,否则不用递增递减运算符的后置版本

why?前置版本把值+1然后返回改变了的运算对象;后置版本需要将原始值存储下来以便返回未修改的内容

 

后置递增运算符的优先级要高于解引用运算符,*pbeg++等价于*(pbeg++)

4.6 成员访问运算符

箭头运算符:ptr->mem等价于(*ptr).mem

点运算符:

4.7 条件运算符

条件运算符?:把简单的if-else运算逻辑嵌入到单个表达式中:cond ? expr1 : expr2; //只对表达式expr1, expr2中的1个求值

 

条件运算符的优先级非常低,通常需要在两端加上括号

cout << ((grade < 60) ? "fail" : "pass");     //?: 优先于 < 关系运算符

 

4.8 位运算符

4.9 sizeof 运算符

sizeof运算符返回的是一条表达式或者一个类型名字所占的字节数,所得值是一个size_t类型的常量表达式

sizeof (type)

sizeof expr   //返回表达式结果类型的大小,并不实际计算其运算对象的值

sizeof *p //等价于sizeof (*p); 因为sizeof与*优先级相同,且sizeof满足右结合律;又sizeof不会实际计算对象的值,所以即使p是一个无效指针,仍然是一种安全的行为

 

 

sizeof(char)   //1

sizeof(引用对象) //被引用对象所占空间的大小

sizeof(指针) //指针所占空间的大小

sizeof(*指针) //指针所指对象所占空间大小

sizeof(数组) //整个数组所占空间大小,!!!sizeof不会把数组转化为指针处理

sizeof(vector)  //返回vector中固定部分大小

sizeof(ia)/sizeof(*ia)  //返回ia的元素个数

 

4.11 类型转换

隐式类型转换

数组转换成指针:数组自动转换成指向数组首元素的指针

但是decltype关键字,取地址符&,sizeof,dypeid等时,不会发生数组转换成指针

 

指针转换:

常量整数值0、字面值nullptr可以转化为任意指针类型,

任意指针类型可以转为void*;

任意对象的指针能转化为const void*

 

常量转换:

指向非常量类型的指针转换成指向相应常量类型的指针;

引用。。。

 

 

4.11.3 显式转换

强制类型转换cast: cast_name<type>(expression)

 

4种

 

static_cast

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

double slope = static_cast<double>(j)/i;

static_cast强制转换的结果将于原始的地址值相等,因此必须确保转换后所得的类型就是指针所指的类型

const_cast

const_cast只能改变对象的底层const,不能改变表达式类型;=

const char *pc;

char *p = const_cast<char*>(pc);  //正确,但是通过p写值是未定义的行为(如果对象是一个常量,通过const_cast执行写操作会产生未定义行为)

 

reinterpret_cast

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释

int *ip;

char *pc = reinterpret_cast<char*>(ip);  //pc所指的真实对象是一个Int而不是char字符

reinterpret_cast体现了 C++ 语言的设计思想:用户可以做任何操作,但要为自己的行为负责。

(比如上述程序可以通过,但是编译器不知道pc真实指向的是什么)

dynamic_cast运算符

使用形式:

dynamic_cast<type*>(e);  //e必须是一个有效的指针

dynamic_cast<type&>(e);  //e必须是一个左值

dynamic_cast<type&&>(e);  //e不能是左值

其中,type必须是类类型,并且通常情况下该类应该含有虚函数

 

e必须满足下列条件之一:

1、e的类型是目标type的公有派生类

2、e的类型是目标type的公有基类

3、e的类型就是目标type的类型

 

dynamic_cast的目标类型是指针的话,失败了为0;目标类型是引用的话,失败了抛出一个bad_cast异常

 

用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象。

 

dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。

 

那该如何判断引用转换是否安全呢?不存在空引用,因此不能通过返回值来判断转换是否安全。C++ 的解决办法是:dynamic_cast 在进行引用的强制转换时,如果发现转换不安全,就会拋出一个异常,通过处理异常,就能发现不安全的转换。

旧的强制类型转换

类型名作为强制类型转换运算符的做法是C语言的老式做法

type (expr);   //函数形式的强制类型转换

(type) expr;     //C语言风格的强制类型转换

 

C++ 引入新的强制类型转换机制,主要是为了克服C语言强制类型转换的以下三个缺点。

1) 没有从形式上体现转换功能和风险的不同。

从Int到double是没有风险的,但是从基类指针转换为派生类指针,或者常量指针转换为非常量指针,都是有风险的

2) 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。

3) 难以在程序中寻找到底什么地方进行了强制类型转换。

4.12 运算符优先级表(p147)

作用域>成员访问,下标>后置++>前置++,一元正负号,解引用,取地址,sizeof>乘除法>加减法>左右移位>关系运算符>位运算符>逻辑运算符>条件运算符>赋值运算符

 

//来自c.biancheng.net/view/410.html
#include <iostream>
using namespace std;
class A
{
public:
    operator int() { return 1; }  //函数操作符,可以实现对象当函数使用
    operator char*() { return NULL; }
};
int main()
{
    A a;
    int n;
    char* p = "New Dragon Inn";
    n = static_cast <int> (3.14);  // n 的值变为 3
    n = static_cast <int> (a);  //调用 a.operator int,n 的值变为 1
    p = static_cast <char*> (a);  //调用 a.operator char*,p 的值变为 NULL
    n = static_cast <int> (p);  //编译错误,static_cast不能将指针转换成整型
    p = static_cast <char*> (n);  //编译错误,static_cast 不能将整型转换成指针
    return 0;
}

变量:

1、当i是一个整数的时候i++和++i那个更快?它们的区别是什么?(1)

前置版本:将运算对象+1,然后将改变后的对象作为求值结果

后置版本:将运算对象+1,返回改变之前的对象值的副本

2、32位,64位系统中,各种常用内置数据类型占用的字节数?(1)

转载:https://www.cnblogs.com/reality-soul/p/6141074.html 

在32位、64位系统当中,唯一改变的是指针的长度;在32位系统当中是4个字节、64位则是8个字节。所谓的32位、64位,这个指的是寄存器的位宽。

32位编译器:

      char :1个字节
      char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
      short int : 2个字节
      int:  4个字节
      unsigned int : 4个字节
      float:  4个字节
      double:   8个字节
      long:   4个字节
      long long:  8个字节
      unsigned long:  4个字节

  64位编译器:

      char :1个字节
      char*(即指针变量): 8个字节
      short int : 2个字节
      int:  4个字节
      unsigned int : 4个字节
      float:  4个字节
      double:   8个字节
      long:   8个字节
      long long:  8个字节
      unsigned long:  8个字节

3、结构体内存对齐问题?结构体/类大小的计算?(1)

4、结构体struct和共同体union(联合)的区别(2)

5、C++的四种强制转换(2)见前面笔记

参考:https://www.cnblogs.com/chio/archive/2007/07/18/822389.html

static_cast

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。

 

它主要有如下几种用法:

  • 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  • 把void指针转换成目标类型的指针(不安全!!)必须确保转换后所得的类型就是指针所指的类型。类型一旦不符合,将产生未定义的后果
  • 把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

dynamic_cast

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

 

 

 

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全(dynamic_cast有运行时检查)。

 

// Leetcode.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include<iostream>
using namespace std;

class CBasic {
public:
	virtual int eat() { return 0; }
};

class CDerived : public CBasic {
public:
	virtual int eat() { return 1; }
};

int main() {
	CBasic basic;
	CDerived derived;
	CBasic *ptr1 = &basic;
	CDerived *ptr2 = &derived;
	CBasic *ptr3 = &derived;   //ptr3实际指向的是派生类

	//使用dynamic_cast上行转换,ptr2实际指向的是派生类,转换成功
	CBasic *pd1 = dynamic_cast<CBasic *>(ptr2);
	if (pd1) cout << "pd1返回 " << pd1->eat() << endl;
	else {
		cout << "pd1转换不成功" << endl;
	}
	CBasic *pd2 = static_cast<CBasic *>(ptr2);
	cout << "pd2返回" << pd2->eat() << endl;

	//使用dynamic_cast下行转换,ptr1实际指向基类,含有不安全操作,dynamic_cast发挥作用返回null
	CDerived *pd3 = dynamic_cast<CDerived *>(ptr1);
	CDerived *pd4 = static_cast<CDerived *>(ptr1);
	if (pd3) cout << "pd3返回 " << pd3->eat();
	else {
		cout << "pd3转换不成功" << endl;
	}
	cout << "pd4返回" << pd4->eat() << endl;

	//使用dynamic_cast下行转换,ptr3实际指向派生类,没有问题
	CDerived *pd5 = dynamic_cast<CDerived *>(ptr3);
	if (pd5) cout << "pd5返回 " << pd5->eat() << endl;
	else {
		cout << "pd5转换不成功" << endl;
	}

	

	return -1;
}
/*
pd1返回1
pd2返回1
pd3转换不成功
pd4返回0
pd5返回1
*/

 

6、C++中的基本数据类型及派生类型(2)

1)整型 int

2)浮点型 单精度float,双精度double

3)字符型 char

4)逻辑型 bool

5)控制型 void

基本类型的字长及其取值范围可以放大和缩小,改变后的类型就叫做基本类型的派生类型。派生类型声明符由基本类型关键字char、int、float、double前面加上类型修饰符组成。

类型修饰符包括:

>short 短类型,缩短字长

>long 长类型,加长字长

>signed 有符号类型,取值范围包括正负值

>unsigned 无符号类型,取值范围只包括正值
————————————————
版权声明:本文为CSDN博主「木士易」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yangxueyangxue/article/details/95591285

7、什么是“引用”?申明和使用“引用”要注意哪些问题?(3)

1、"引用"其实是引用对象的别名,不是一个对象;

2、引用要初始化,引用不能修改绑定的对象

3、引用不是一种新的数据类型,因此它本身不占存储单元,系统也不会为它分配存储单元。

4、不能建立引用的数组,但是可以建立数组的引用

int &ref[3] = {2, 3, 5}; //错误,不能建立引用的数组,下标优先级高于& *, int *ref[3],是一个数组,数组元素是int*

//声明引用数组没有办法分配空间,但是指针是一个对象,所以存在指针数组,不存在引用数组

int arr[3]; int (&tef)[3] = arr;  //正确

 

8、将“引用”作为函数参数有哪些特点?(3)

1、引用是别名,对引用形参的修改将作用在被引用的实参上;

2、使用一般变量(传值/传指针,传指针本质上是传值)给函数传递实参时,当函数发生调用时,需要给形参分配存储空间,形参是实参的副本

3、传递的是对象时,还需要调用对象的拷贝构造函数

因此,当传递的数据较大时,传引用比一般参数传递的效率要高

9、在什么时候需要使用“常引用”?(3)

1、提高函数参数传递效率,但是不希望被引用的数据在函数等调用中被改变

2、非常量的引用不能指向临时对象

3、函数的参数设为const 类型,能正常的兼容常量作为参数传递的问题,反之则不行

 

转载:C++标准的规定:非常量的引用不能指向临时对象

例如:

void conv(string &str) { }
int main() {
    conv("dasd"); // 这里错了,编译器自动生成一个string(“dasd”)临时对象,不能将该临时对象传给非const引用
}

因此,需要将其改为:

void conv(string str) { } // 值传递

或者

void conv(const string &str) { } // const引用,因为标准规定临时对象是不能更改的,所以要加上const修饰。

10、将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?(3)

转载:https://blog.csdn.net/weibo1230123/article/details/82014538

好处:在内存中不产生被返回值的副本;

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }

规则:

  • 不能返回局部变量的引用。当函数返回时,局部变量会被销毁
  • 不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。(防止memory leak)
  • 可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  • 流操作符重载返回值申明为“引用”的作用:流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
  •  在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

11、分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。(3)

bool:  if(a)  or if(!a)   //表明其为“逻辑”判断

int(short、int、long等): if(a == 0) //表明与0进行数值上比较

float:  const EXPRESSION exp = 0.000001; if(a < EXP && a > -EXP)  //浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较

point if(a == NULL)

 

在实际的软件编程中。const EXPRESSION exp明确了告诉编码者,说EXP是一个精度类型。
这个精度类型是通过typedef float EXPRESSION;定义的。

 

 

 

float与double的范围和精度以及大小非零比较

1. 范围
  float和double的范围是由指数的位数来决定的。
  float的指数位有8位,而double的指数位有11位,分布如下:
  float:
  1bit(符号位) 8bits(指数位) 23bits(尾数位)
  double:
  1bit(符号位) 11bits(指数位) 52bits(尾数位)
  于是,float的指数范围为-127~+128,而double的指数范围为-1023~+1024,并且指数位是按补码的形式来划分的。
  其中负指数决定了浮点数所能表达的绝对值最小的非零数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围。
  float的范围为-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。

2.  精度
  float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
  float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
  double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。

//转载:https://www.cnblogs.com/marvin-notes/p/4369111.html
const float EPSINON = 0.000001; 

// 非零判断
if ((f >= - EPSINON) && (f <= EPSINON))
{ 
    // f == 0
}

// 大小比较
if ((abs(f1-f2) >= - EPSINON) && (abs(f1-f2) <= EPSINON))
{
     // (f1-f2) == 0, 即 f1 == f2
}

 

12、int id[sizeof(unsigned long)];这个对吗?为什么?(3)

正确,正确 这个 sizeof是编译时运算符,编译时就确定了  ,且sizeof返回值是一个size_t的常量表达式,所以可以用来声明数组的维数

13、全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是
怎么知道的?(3)

全局变量:定义在函数体外的对象,存在于程序的整个执行过程,程序启动时创建,结束时销毁

局部变量:函数形参与函数体内定义的变量,其声明周期依赖于定义的方式

操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值