目录
2.1 基本内置类型
关于int
和long
int
能表示的范围在 -2^31 ~ 2^31-1
之间,如果超出,则需要用 long
有符号数和无符号数
切记:不要混用有符号数和无符号数!!!
因为,两者在同一个表达式时,有符号数会转为无符号数!!
八进制和十六进制的表示
八进制数用数字0开头表示,每一位用0、1、2、3、4、5、6、7八个数码表示;
十六进制通常用数字0、1、2、3、4、5、6、7、8、9和字母A、B、C、D、E、F(a、b、c、d、e、f)表示,其中:A~F表示10~15
2.2 变量
初始化和赋值的区别
完全不是一个概念!
列表初始化
注意,列表初始化是C++11的初始化方式,不是初始化列表,初始化列表是构造函数里面用到的一个概念。
举例:
注意:如果初始化的时候存在信息丢失的风险,那么无法通过编译!
因为这样会丢失小数点后面的数,从而初始化错误!
通过编译,输出是3。
初始化的注意事项
定义于任何函数体之外的变量都被初始化为0:
如果在函数体内定义内置类型,但没有初始化,如果试图访问或复制,会报错:
如果是非内置类型,不会报错,例如string
:
(注:内置类型,包括整形和浮点型)
编程习惯!
声明和定义
声明的关键字是 extern
void test()
{
extern int a;
cout << &a; // 错误,因为a只是声明,没定义(没有开辟内存空间)
int b;
cout << &b; // 正确,因为定义了b,从而开辟了内存空间
extern int c = 10; // 错误,因为在函数内部不能显示初始化
}
extern int d = 10; // 正确,因为这是在函数外部,但这样做就变成了定义,失去了extern的作用了
int main()
{
...
}
注:
一条语句声明多个变量
顺序:从左到右
正确:
int a = 10, &r = a, b = 10;
cout << &a << endl << sizeof(a) << endl << &b << endl;
错误:
int &r = a, a = 10, b = 10;
cout << &a << endl << sizeof(a) << endl << &b << endl;
从地址角度,b在a后面:
静态类型
这也是为什么必须要声明类型的原因,为了让编译器能够在编译阶段检查类型是否正确!
2.3 复合类型
引用和初始化的关系
初始化:初始值会被拷贝到新建的对象中。
引用:定义引用后,会和它的初始值绑定在一起,只是绑定而已,而不是把初始值拷贝给引用。
--> 解释了引用为什么必须要被初始化
引用和变量地址的关系
- 引用只是起别名,不论起多少个别名,都指的是同一个
a
,地址自然都是a
的地址:
- 引用不是对象,因此没有实际地址。
合法&不合法的引用
要注意第9
行和第12
行的区别,"引用和对象严格匹配",指的是在初始化引用的时候要严格匹配数据类型,初始化之后就变成了变量见的赋值了:
void test()
{
int a = 10;
int& r1 = a;
double b = 3.14;
double& r2 = b;
r2 = r1; // 合法, r1的值赋值给r2, 即 a的值赋值给b
int c = 20;
double& r3 = c; // 非法, 类型不同,无法初始化
}
引用和指针的符号说明
注意最后一个,*p
是一个对象,不是字面值常量,所以可以用来初始化引用&r2
空指针 nullptr和NULL
C++11引入的新方法,用nullptr
字面值来初始化指针,得到空指针:
int *p = nullptr; // 等价于 int *p = 0;
注:尽量使用nullptr
编程习惯!
赋值和指针、函数内和函数外的区别
void test()
{
int* p1 = 0;
cout << p1 << endl; // 输出0000000000000000 -> 空指针
int i = 10;
int* p2 = &i;
cout << p2 << endl; // 输出 00000005DB74F724 -> i的地址
p2 = 0; // 这句的意思是, 让p2指向空
cout << p2 << endl; // 输出0000000000000000 -> 空指针
int* p3;
cout << p3 << endl; // 报错, 函数内定义指针, 如果不初始化就访问, 会报错!
}
int* p4; // 函数外定义, 可以不手动初始化, 系统自动初始化为空指针, 并且可以访问!
int main()
{
test();
cout << p4 << endl; // 输出0000000000000000 -> 空指针
return 0;
}
关于函数内和函数外的小结:
参考 2.2 变量-初始化的注意事项的笔记,函数外定义但不初始化,可以访问;函数内定义但不初始化,就无法访问。
关于变量声明写法的一些容易误导的地方
int i = 10, *p = &i, &r = i; // 完全合法
指的是, 定义个一个int变量i, 定义了一个指针p指向i, 定义了一个引用r并用i来初始化
void test()
{
int a = 10;
int* p1, p2, &p3 = a; // 定义一个指向int型指针p1, int变量p2, 引用p3并用a来初始化
p2 = 20;
cout << p2 << endl; // 20
cout << p3 << endl; // 10
p1 = &p3; // p1指向a
cout << *p1 << endl; // 10
}
注意, int* p1, p2这样的写法容易误导, 会误认为是 后面的变量全都是指向int型的指针;
因此, int *P1, p2这样写会更好
2.4 const 限定符
初始化和有效范围
const对象必须被初始化,因为:一旦创建就不能被修改。
有效范围:
默认情况下,const对象只在当前文件内有效。原因:
如果希望不同文件只定义一个const常量,那么必须在变量的定义之前加上extern
:
const的初始化和引用
初始化:
int a = 10;
const int &r1 = a; // 合法
const int &r2 = 20; // 合法
int &r2 = 20; // 错误! 右边是数字, 必须要用const
int &r3 = r1; // 错误! 因为r1是const引用, 而r3是非const引用(不匹配)
注意
double a = 3.14;
const int &b = a; // 合法
上面两句在编译阶段:
const int temp = a; // 先生成一个整形临时变量
const int &b = temp; // 再对该临时变量进行引用
但是:
double a = 3.14;
int &b = a; // 错误! 如果不是同一个类型, 必须要加const
int a = 10;
int &r1 = a;
const int &r2 = a;
r1 = 20; // 合法
r2 = 20; // 错误!
a = 20; // 合法
const int &r2 = a;
只不过说的是:不能通过r2
去修改a
,但完全可以通过其它方式修改a
,例如第4、6
行。
正确,因为const引用可以绑定非const变量
int a = 10;
const int &b = a;
错误,因为非const引用不能绑定const变量(不然就可以通过引用修改值了)
const int a = 10;
int &b = a;
注,上面是引用,如果换成指针,依然成立,例如:
正确,因为指向const的指针b可以指向非const变量a
int a = 10;
const int *b = &a;
错误,因为指向非const的指针b不能指向const变量a(不然就可以通过指针修改值了)
const int a = 10;
int* b = &a;
const指针
定义:指向的地址不能变,但地址里的值可以变。
写法:*const
代表常量指针
void test()
{
int a = 10;
int* const p = &a;
*p = 20; // 合法, 因为地址里的值可以改
cout << a;
int b = 20;
p = &b; // 错误, 因为指针p指向的地址不能修改
}
易混淆
- 指向常量的指针。
写法:const int* p = &a;
概念:要想存放常量的地址,只能使用指向常量的指针;即,不能通过指向常量的指针来修改指向的值,但可以修改这个指针指向的对象,比如const int* p
开始指向a
,可以让p
指向b
。
- 常量指针。
写法:int *const p = &a;
概念:指针指向的对象无法修改,一开始指向谁,就永远指向谁,但可以修改指向对象的值,比如int *const p
指向a
,开始a=10
,现在可以通过p
让a=20
,但不能让p
指向b
。
- 指向常量的常量指针。
写法:const int *const p = &a;
概念:因为是指向常量的指针,所以不能通过p
修改a
的值,又因为是常量指针,所以不能修改p
指向的对象。
小结:能修改哪些值,要看const修饰的是谁。
易错例子
1. int i, *const cp;
不合法。因为常量指针cp
必须初始化。
2. const int ic, &r = ic;
不合法。因为const对象必须初始化,而ic
没有被初始化。
3. int i = -1, &r = 0;
不合法。因为r
是非常量引用,不能引用字面值常量0。
4. const int i = -1, &r = 0;
合法。因为r
是常量引用,可以引用字面值常量0,即const int &r = 0;
常量表达式
声明加上前缀:constexpr
constexpr和指针
constexpr
的初始化:必须是nullptr
或0
或存储于某个固定地址的对象。
注:
void test()
{
int a = 10;
constexpr int* p = &a; // 错误,因为a定义在函数体内,a不是存放在固定地址中
}
int a = 10;
int main()
{
constexpr int* p = &a; // 正确,因为a在函数体外,认为是在固定地址中的
return 0;
}
2.5 处理类型
using类型别名
传统方法:typedef
新方法:using
auto
- 在同一行声明多个变量,那么需要保证相同的类型
auto
一般会忽略掉顶层const
void test()
{
int i = 10, & r = i;
auto a = r;
const int ci = i, &cr = ci; // ci是顶层const, cr也是顶层const
auto b = ci; // b只是int, 因为忽略了ci的顶层const特性
auto c = cr; // c只是int, cr就是ci, 然后又忽略了ci的顶层const特性
b = 20; // 从7行知,b可以修改
c = 20; // 从8行知,c可以修改
auto d = &i; // 注意,这里d是一个指针,是int型的
*d = 20; // 也是只int型的, 自然可以修改
auto e = &ci; // e是一个指针, 且指向const常量ci,
//*e = 20; // 错误, 由上知, ci不可修改
e = &b; // e只是一个普通指针(忽略了ci的顶层const特性),因此可以修改指向对象
cout << i << endl << ci << endl << c;
}
decltype
作用:返回操作数的操作类型。
特点:包含了顶层const
(和auto
不同)
例子:
void test()
{
const int a = 10, &r = a;
decltype(a) b = 20; // 保留a的顶层const特性, 因此b是const int型
b = 30; // 错误, 由上知b不能修改
int c = 10;
decltype(r)d = c; // 保留r的顶层const特性, 因此d是const引用
d = 30; // 错误, 由上知不能通过d修改c
c = 30; // 正确, 因为只是不能通过d去修改c而已, 而c本身只是int, 可以随意修改
}
void test1
{
int a = 3, b = 4;
decltype(a = b) c = a; // 如果a是int, 那么a=b返回的类型是int&
cout << a;
// a是3,不是4,因为decltype(a = b)里面并不会计算,只是根据这个返回类型,因此a不会被赋值
}
记住:
2.6 自定义数据结构
预处理器概述
作用:在编译之前执行的一段程序,确保头文件多次包含也能安全工作。
头文件保护符 #ifndef #define #ifdef #endif
下面给一个#ifndef/#define/#endif的格式:
#ifndef A_H意思是"if not define a.h" -->如果不存在a.h
接着的语句应该#define A_H -->就引入a.h
最后一句应该写#endif -->否则不需要引入
#ifndef GRAPHICS_H // 防止graphics.h被重复引用
#define GRAPHICS_H
#include <math.h> // 引用标准库的头文件
…
void Function1(…); // 全局函数声明
…
class Box // 类结构声明
{
…
};
#endif