变量提供一个具名的、可供程序操作的存储空间。 C++
中变量和对象一般可以互换使用。
定义形式:类型说明符(type specifier) + 一个或多个变量名组成的列表。如int sum = 0, value, units_sold = 0;
初始化(initialize):对象在创建时获得了一个特定的值。
{}
,如int units_sold{0};
注意:使用列表初始化时,如果初始值存在信息丢失的风险,编译器将报错:
long double ld = 3.1415926536;
int a{ld},b{ld}; //错误:转换未执行,因为存在丢失信息的危险
int c(ld),d = ld; //正确:转换执行,丢失部分值
C++
将声明和定义区分开。声明使得名字为程序所知。定义负责创建与名字关联的实体。extern
,如extern int i;
。但如果包含了初始值,就变成了定义:extern double pi = 3.14;
变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。
名字的作用域(namescope){}
嵌套的作用域
::reused
显式访问全局变量reused。练习2.11
个人理解:左值是具有地址属性的对象。左值可以出现在=左边与=右边。
int i = 10; //i是左值
++i; //左值,地址为i的地址
个人理解:不是左值的对象就是右值。或者说无法操作地址的对象就叫做右值。右值只能出现在=右边。
int i2 = i + 1; // i + 1是一个临时对象,它有地址属性,但这个地址属性无法被使用,因此为右值
int i = 10;
i++; //右值,先返回一个临时变量,临时变量的地址无法被使用,可以视作没有地址属性
一条声明语句由一个基本数据类型(base type)和紧随其后的一个声明符(declarator)列表组成;
说明下列变量的类型和值。
(a) int* ip, i, &r = i;
(b) int i, *ip = 0;
(c) int* ip, ip2;
解:
一般说的引用是指的左值引用
int &refVal = val;
。个人理解:引用是阉割版的指针,引用不是一个对象,而是给对象起的一个别名。
引用定义时必须被初始化,并且与初始化的值绑定在一起,不像指针能够通过+或-运算符来指向其它内存地址;
下面的哪个定义是不合法的?为什么?
解:
(b)和(d)不合法,(b)引用必须绑定在对象上而不是一个常量,(d)引用必须初始化。
考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?
int i = 0, &r1 = i;
double d = 0, &r2 = d;
解:
int *p; //指向int型对象的指针
是一种 "指向(point to)"
另外一种类型的复合类型。
定义指针类型: int *ip1;
,从右向左读有助于阅读:1. ip1
是指针;2. 一个指向int
类型的指针。
指针存放某个对象的地址。
引用不是对象,没有实际地址,因此不能定义指向引用的指针。
获取对象的地址: int i=42; int *p = &i;
。 &
是取地址符。
指针的类型与所指向的对象类型必须一致(均为同一类型int、double等)
指针的值的四种状态:
1.指向一个对象;
2.指向紧邻对象的下一个位置;
3.空指针;
4.无效指针。
> 对无效指针的操作均会引发错误,第二种和第三种虽为有效的,但理论上是不被允许的
指针访问对象: cout << *p;
输出p指针所指对象的数据, *
是解引用符。
不能把int变量直接赋给指针
空指针不指向任何对象。使用int *p=nullptr;
来使用空指针。
指针和引用的区别:引用本身并非一个对象,引用定义后就不能绑定到其他的对象了;指针并没有此限制,相当于变量一样使用。
赋值语句永远改变的是左侧的对象。
void*
指针可以存放任意对象的地址。能存,能赋值,但不能取:因无类型,仅操作内存空间,对所存对象无法访问。
其他指针类型必须要与所指对象严格匹配。
两个指针相减的类型是ptrdiff_t
。
建议:初始化所有指针。
int* p1, p2;//*是对p1的修饰,所以p2还是int型
其他指针操作
请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
int i = 0;
- (a) double* dp = &i;
- (b) int *ip = i;
- (c) int *p = &i;
解:
double
的指针指向 int
。int
变量赋给指针。给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。
解:
能,可以使用try catch的异常处理来分辨指针p是否指向一个合法的对象,但通过普通控制结构无法实现。
在下面这段代码中为什么 p 合法而 lp 非法?
int i = 42;
void *p = &i;
long *lp = &i;
解:
void *
是从C语言那里继承过来的,可以指向任何类型的对象。 而其他指针类型必须要与所指对象严格匹配。
在C++中,extern const
是用来声明外部链接的常量的关键字组合。
关键字extern
用于声明一个变量或常量是在其他地方定义的,它告诉编译器该变量或常量的定义在其他文件中。这样,在当前文件中使用这个变量或常量时,编译器会在链接过程中找到它的实际定义。
关键字const
表示该变量或常量是一个不可修改的值。它告诉编译器该标识符所表示的值在程序执行期间不会改变。
通过将extern const
结合使用,我们可以声明一个外部链接的常量,该常量的定义位于其他文件中,并且在当前文件中不可修改。
例如,假设我们有两个文件:file1.cpp和file2.cpp。在file1.cpp中定义了一个常量:
// file1.cpp
extern const int MY_CONSTANT = 10;
然后,在file2.cpp中可以使用这个常量:
// file2.cpp
extern const int MY_CONSTANT;
int main() {
// 使用MY_CONSTANT
int value = MY_CONSTANT;
// ...
return 0;
}
在这个例子中,extern const int MY_CONSTANT
的声明告诉编译器,MY_CONSTANT
的定义在其他文件中。在file2.cpp中使用MY_CONSTANT
时,编译器会在链接过程中找到它的实际定义并使用该值。同时,由于使用了const
关键字,MY_CONSTANT
的值在程序执行期间是不可修改的。
const int buf; // 不合法, const 对象必须初始化
int cnt = 0; // 合法
const int sz = cnt; // 合法
++cnt; ++sz; // 不合法, const 对象不能被改变
const int ival=1; const int &refVal = ival;
,可以读取但不能修改refVal
。也就是底层const,因为const自带顶层const个人理解:临时量没有地址属性,也就是一个右值。但因为是常量引用,以后都不会修改temp的值,也就用不到temp的地址属性,因此这种赋值是合法的。
个人理解:因为普通的引用需要使用到被指变量的地址属性,而临时量是一个右值,没有地址属性,因此非法。
const double pi = 3.14; const double *cptr = π
。int i = 0; int *const ptr = &i;
下面的哪些初始化是合法的?请说明原因。
解:
int i = -1, &r = 0; // 不合法, r 必须引用一个对象
int *const p2 = &i2; // 合法,常量指针
const int i = -1, &r = 0; // 合法
const int *const p3 = &i2; // 合法
const int *p1 = &i2; // 合法
const int &const r2; // 不合法, r2 是引用, 引用自带顶层 const, 第二个const写法多余但合法, 但引用需要初始化.
const int i2 = i, &r = i; // 合法
说明下面的这些定义是什么意思,挑出其中不合法的。
解:
//const修饰的变量必须初始化,无论是内置类型还是指针。
int i, *const cp; // 不合法, const 指针必须初始化
int *p1, *const p2; // 不合法, const 指针必须初始化
const int ic, &r = ic; // 不合法, const int 必须初始化
const int *const p3; // 不合法, const 指针必须初始化
const int *p; // 合法. 一个指针,指向 const int
假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
解:
i = ic; // 合法, 常量赋值给普通变量
p1 = p3; // 不合法, p3 是const指针不能赋值给普通指针
p1 = ⁣ // 不合法, 普通指针不能指向常量
p3 = ⁣ // 不合法, p3 是常量指针且指向常量, 故p3 不能被修改, 本句赋值语句正在修改
p2 = p1; // 不合法, p2是常量指针, 有顶层const, 不能被修改
ic = *p3; // 不合法, 对 p3 取值后是一个 int 然后赋值给 ic, 但ic是常量不能被修改
对于p1 = p3:
int i = 0;
const int *const p3 = &i;
int *p1;
p1 = p3;
错误出在第一个const上,因为编译器不知道p3指向的到底是常量还是变量,因此默认p3指向的是一个常量整数。所以p3不能乱赋值给一个非常量的int指针。
因此,编译器会报错,指出不能将const int *const类型的指针赋值给int*类型的指针,因为它们的常量性不匹配。
顶层const
:指针本身是个常量。底层const
:指针指向的对象是个常量。拷贝时严格要求相同的底层const资格。对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?
const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2, *const p3 = &i, &r2 = v2;
解:
v2 是顶层const,p2 是底层const,p3 既是顶层const又是底层const,r2 是底层const。
假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
解:
r1 = v2; // 合法, 顶层const在拷贝时不受影响
p1 = p2; // 不合法, p2 是底层const,如果要拷贝必须要求 p1 也是底层const
p2 = p1; // 合法, int* 可以转换成const int*
p1 = p3; // 不合法, p3 是一个底层const,p1 不是
p2 = p3; // 合法, p2 和 p3 都是底层const,拷贝时忽略掉顶层const
constexpr
和常量表达式(▲可选)字面值属于常量表达式,用字面值初始化的const对象也是常量表达式。
C++11
新标准规定,允许将变量声明为constexpr
类型以便由编译器来验证变量的值是否是一个常量的表达式。
#include
#include
int main(void)
{
int i = 1;
constexpr int &i2 = i; //报错,i不是常量表达式
std::cout<<i2<<std::endl;
i++;
std::cout<<i2<<std::endl;
return 0;
}
#include
#include
int main(void)
{
int i = 1;
const int &i2 = i; //成功运行
std::cout<<i2<<std::endl;
i++;
std::cout<<i2<<std::endl;
return 0;
}
指针的constexpr
下面的代码是否合法?如果非法,请设法将其修改正确。
int null = 0, *p = null;
解:
非法,即使int的值恰好是0,也不能直接给指针赋值int变量。应改为
int null = 0, *p = &null;
而且应该注意到,null都是小写,并不是关键字或者预处理变量。
传统别名:使用typedef来定义类型的同义词。 typedef double wages;
新标准别名:别名声明(alias declaration): using SI = Sales_item;
(C++11)
// 对于复合类型(指针等)不能代回原式来进行理解
typedef char pstring; // pstring是char的别名
const pstring cstr = 0; // 指向char的常量指针
// 如改写为const char *cstr = 0;不正确,为指向const char的指针
// 辅助理解(可代回后加括号)
// const pstring cstr = 0;代回后const (char *) cstr = 0;
// const char *cstr = 0;即为(const char *) cstr = 0;
auto类型说明符:让编译器自动推断类型。
一条声明语句只能有一个数据类型,所以一个auto声明多个变量时只能相同的变量类型(包括复杂类型&和*)。auto sz = 0, pi =3.14//错误
会忽略引用:int i = 0, &r = i; auto a = r;
推断a
的类型是int
,因为r实际是指向i的,r只是i的一个别名。
#include
#include
#include
#include
#include
using boost::typeindex::type_id_with_cvr;
int main()
{
int i = 100;
const int& refI = i;
auto i2 = refI;
//输出int
std::cout << type_id_with_cvr
return 0;
}
会忽略顶层const
。
auto关键字在推断类型时,如果没有引用符号,会忽略值类型的const修饰,而保留修饰指向对象的const,典型的就是指针。
会忽略第二个const而保留第一个const
即会忽略顶层const
,而保留底层const
即pi2的类型是const int *
理解:auto pi2 = pi;
此时pi2与pi指向同一块地址,但当pi2发生变化,比如指向下一块地址时,不会影响到pi仍然指向原地址。所以const修饰符被忽略。
而因为pi2有直接修改原地址(i=100所在的地址)中i的值的能力,为了防止pi2对const类型i进行修改,所以pi2的类型为const int*,即i为const
运行结果:const int *
const int ci = 1; auto f = ci;
推断类型是int
,如果希望是顶层const需要自己加const
,const auto f = ci
个人理解:
auto f = ci; //值传递,f没能力改变ci的值,因此auto推断出来的类型不带const
const int ci = 1;
auto &f = ci;
std::cout << type_id_with_cvr<decltype(f)>().pretty_name() << std::endl; //此时则为const int &
判断下列定义推断出的类型是什么,然后编写程序进行验证。
const int i = 42;
auto j = i; const auto &k = i; auto *p = &i;
const auto j2 = i, &k2 = i;
解:
j 是 int,k 是 const int的引用,p 是const int *,j2 是const int,k2 是 const int 的引用。
9
decltype(f()) sum = x;
推断sum
的类型是函数f
的返回类型。顶层const
。C++11
decltype
和auto
是C++11引入的两个类型推导机制,用于在编译时自动推导变量的类型。尽管它们可以实现类似的功能,但它们有以下区别:
decltype
:decltype
从表达式中推导出变量的类型。它可以获取变量、函数调用、表达式等的类型,包括修饰符和引用。auto
:auto
用于推导变量的初始值表达式的类型。它根据变量初始化的值来确定类型,并且通常会忽略顶层的const
和引用修饰符。decltype
:decltype
在编译时对表达式进行推导,因此可以用于推导尚未初始化的变量的类型。auto
:auto
在编译器遇到变量声明时进行推导,要求变量必须被初始化,以便从初始值推导出类型。const
的处理不同:decltype
:decltype
保留变量的引用和顶层const
限定符。auto
:auto
推导的类型会去除引用和顶层const
限定符,得到非引用类型。下面是一些示例代码,用于展示decltype
和auto
的区别:
int x = 10;
const int& ref = x;
decltype(ref) a = x; // 推导为 const int&
auto b = ref; // 推导为 int
decltype(x + 1) c = x; // 推导为 int
auto d = x + 1; // 推导为 int
decltype((x)) e = x; // 推导为 int&
auto f = (x); // 推导为 int,去除了引用修饰符
decltype(x) g; // 正确,推断为未初始化的int类型,decltype不需要初始化表达式
auto h; // 编译错误,auto 需要初始化表达式
综上所述,decltype
和auto
在类型推导时有一些区别:
decltype
能够推导出表达式的准确类型,包括修饰符和引用。auto
只能根据初始值的表达式推导出变量的类型,不考虑修饰符和引用。const
的处理:decltype
会保留变量的引用和顶层const
限定符。auto
会去除引用和顶层const
限定符。decltype
在编译时对表达式进行推导,可以用于推导尚未初始化的变量的类型。auto
在编译器遇到变量声明时进行推导,要求变量必须被初始化。decltype
通常用于需要获取表达式类型的情况,比如模板元编程或函数返回类型推导。auto
通常用于简化代码书写,尤其是在迭代器、范围循环等场景下,让编译器自动推导类型。需要注意的是,由于auto
是在编译时进行类型推导,因此它不能用于推导运行时动态类型的情况,例如函数参数的类型、函数返回类型无法使用auto
进行推导。
尽量不要把类定义和对象定义放在一起。如
struct Student{} xiaoming,xiaofang;
struct
开始,紧跟类名和类体。C++11
:可以为类数据成员提供一个类内初始值(in-class initializer)。const
和constexpr
变量。预处理器概述:
预处理器(preprocessor):确保头文件多次包含仍能安全工作。
当预处理器看到#include
标记时,会用指定的头文件内容代替#include
头文件保护符(header guard):头文件保护符依赖于预处理变量的状态:已定义和未定义。
#indef
已定义时为真#inndef
未定义时为真#ifndef SALES_DATA_H //SALES_DATA_H未定义时为真
#define SALES_DATA_H
strct Sale_data{
…
}
#endif
C++头文件保护符(Header Guard)是一种预处理指令,用于防止头文件被多次包含。当多个源文件包含同一个头文件时,头文件保护符确保头文件只会被编译一次,避免重复定义错误。
通常情况下,头文件保护符使用宏来实现。以下是一个常见的头文件保护符的示例:
#ifndef HEADER_NAME_H
#define HEADER_NAME_H
// 头文件内容
#endif
这里的HEADER_NAME_H
是一个唯一的标识符,可以是任何合法的C++标识符。当编译器首次遇到#ifndef
指令时,如果HEADER_NAME_H
未定义,则继续编译头文件,并定义HEADER_NAME_H
。如果HEADER_NAME_H
已定义,则跳过头文件内容,避免重复编译。
头文件保护符的工作原理如下:
#ifndef
检查指定的标识符是否已定义。#define
指令来定义该标识符,并继续编译头文件内容。#endif
指令结束头文件保护符的区域。使用头文件保护符可以确保头文件只被编译一次,提高编译效率并避免重复定义错误。它是编写C++头文件时的常见做法。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章