学习目标:
变量与基本类型
学习内容:
一、基本数据类型
- 算数类型
char 字符 8位 (-128,127)
short 短整型 16位 -32768~32767
int 整型 32位
long long 长整型 64位
float 单精度浮点数 6位
double 双精度浮点数 10位
无符号类型 unsigned,程序里尽量不要用
字面值常量literal,整型:十进制20, 八进制(0开头)024, 十六进制(0x开头)0x14;字符:‘a’, ‘hello world’
转义序列:换行\n,单引用’,双引用’’
二、变量
-
c++中变量其实就是等于对象。但是一般来说,我们把内置类型相关的称为变量,与类相关的称为对象。
-
初始化是在创建对象的时候给对象赋一个初值,而赋值是把对象当前的值擦除,赋一个新值。
-
建议在创建对象的时候都进行初始化;
-
四种常见变量初始化的方式:
int a = 1; int b = {1}; // 列表初始化 int c(1); int d{1};
但是需要注意的是,列表初始化的初始值如果存在信息丢失(类型转换long double->int)的风险,编译器会报错;
-
声明是让名字被程序所知,一般写在头文件中,声明规定了变量的类型和名字;而定义是负责具体的实现,还要申请存储空间,还可以给变量赋初始值。
声明:extern int i;
声明并定义i:int i; -
标识符
要用具体的含义,不能定义a、b等;
变量名一般用小写字母,如index;
类名一半以大写字母开头,如Sales_item;
变量名是多个单词组成,要区分开:student_loan或studentLoan;
三、复合类型
1. 引用
-
形式:类型名 &别名 = 对象
引用的类型必须和所引用的对象类型一致,除了常量引用;
引用必须要初始化,即右边必须要有一个对象; 代码理解#include <iostream> int main(){ int &a; // 报错 }
-
右边必须是一个已经存在的对象,不能是一个常量;如果想引用常量,必须在前面加const,此时编译器会先生成一个temp对象,再进行引用; 代码理解:
#include <iostream> int main(){ int &a = 1; // 报错 const int &b = 1; // 不报错 // 编译器做的事 // const int temp = 1; // const int &b = temp; }
-
这里提到的引用其实是左值引用;
-
引用即别名,引用只是为一个已经存在的对象取了另外一个名字;
代码理解1:#include <iostream> int main(){ int a = 1; int &b = a; for(int i = 0; i < 10; i++){ a = i; // 输出 0-9 std::cout << b << std::endl; } }
代码理解2:
#include <iostream> int main(){ int a = 1; int &b = a; // 读取a和b的内存地址 std::cout << &a << std::endl; // 0x61fe14 std::cout << &b << std::endl; // 0x61fe14 }
-
引用后不管改动哪个值,两者都会更改; 代码理解:
#include <iostream> int main(){ int a; int &b = a; a = 10; b = 20; // 20 20 std::cout << a << b << std::endl; }
2. 指针
指向地址的变量称为指针变量。 定义:int *a; 使用:a = &b; 也可以合起来写:int *a = &b;
-
int *a = &b;这句话的含义:定义了一个int型的指针变量a,它指向的是int变量b的地址。
-
指针和引用的区别:指针本身就是一个变量,而引用只是为一个已存在的变量取了个别名;指针可以不初始化,和其他变量类型一样,会拥有一个不确定的值;引用无法重新绑定,指针可以随意赋值; &取地址符获取的是a的起始地址;
#include <iostream> int main(){ int *a; std::cout << &a << std::endl; // 0x61fe18 }
-
指针定义的时候 * 说明p是一个指针变量;使用时 * 表示取p保存的地址对应的值; 获取对象的地址用取地址符&,利用解引用符*来访问对象
#include <iostream> int main(){ int a = 1; int *p = &a; // 输出a的(起始)地址 p里面保存的就是a的地址 std::cout << &a << " " << p << std::endl; // 0x61fe14 0x61fe14 // 输出p的起始地址 std::cout << &p << std::endl; // 0x61fe10 // 输出p保存的a的地址对应的值 = a的值 std::cout << *p << std::endl; // 1 }
-
【多重指针】判断指针变量自身的类型和指针变量指向的类型:在定义时,把变量名忽略,剩下的是什么类型这个指针变量就是什么类型;把变量名和前面的一个指针忽略,剩下的是什么类型这个指针指向的就是什么类型;
#include <iostream> int main(){ // 指针变量a自身的类型是int * // 指针变量a指向的类型是int int *a; int a = 1; int *b = &a; // b指向一个int类型的数a int **c = &b; // c指向一个int*型的指针b int **c = &a; // 错 std::cout << a << *b << **c << std::endl; // 三种方式输出a的值 }
-
使用指针时类型应该匹配;
#include <iostream> int main(){ int a = 1; double *b = &a; // 错 double c = 1.1; int *d = &c; // 错 }
-
可以通过指向的对象来改变指针变量所指向地址的值;也可以通过指针变量所指向地址的值来改变指向的对象值;
#include <iostream> int main(){ int a = 1; int *b = &a; a = 2; std::cout << *b << std::endl; // 2 *b = 3; std::cout << a << std::endl; // 3 }
-
注意指针和引用混合使用,容易混淆;
#include <iostream> int main(){ int a = 1; int *b = &a; int &c = b; // 错 b表示的地址 c表示int类型的一个别名 类型不符合 int &c = *b; // 对 *b表示a的值 c表示*b的一个别名 std::cout << c << std::endl; }
-
指针最好都初始化;指针初始化最好用nullptr,不要用NULL,如 int *p = nullptr;、
-
指针比较:两个指针存放的地址相同(全为空 或 指向同一个对象 或指向同一个对象的下一个地址);
-
if(p)任何非零的指针都返回True;if(*p)任何值是非0的返回True;
#include <iostream> int main(){ int i = 0; int *p1 = &i; int *p2 = 0; if (p1){ // True std::cout << "p1" << std::endl; } if (p2){ // False std::cout << "p2" << std::endl; } }
#include <iostream> int main(){ int i = 1; int *p1 = &i; if (*p1){ // 任何值是非0返回True std::cout << "p1" << std::endl; } }
-
void *可以存放任意对象的地址,不管对象是什么类型;
#include <iostream> int main(){ int i = 1; void *p1 = &i; // 合法 float *p2 = &i; // 不合法 }
-
指向指针的引用:引用本身不是对象,因此不存在指向引用的指针,但是存在对指针的引用;
#include <iostream> int main(){ int a = 1; int *b = &a; // b指向一个int型的数 // 【从右往左看】离变量名c最近的是& 说明c是一个引用,int *说明c引用的对象是一个int型的指针 int *&c = b; // c引用一个int型的指针 }
-
定义多个指针变量
#include <iostream> int main(){ // a int型指针 // b int型变量 // c 引用int型变量b // d 引用int型指针a int *a, b, &c = b, *&d = a; }
四、const限定符
const 类型名 变量名 = 初始值
-
const一旦初始化,就无法改变; const定义时一定要初始化;
#include <iostream> int main(){ const int a = 1; a = 2; // 错 const int c; // 错 }
-
const变量可以和其他变量一样参与赋值、运算等操作,但是其他需要改变const变量的操作都会报错;
// 赋值时 不会改变const变量 不会报错 const int a = 1; int b = a;
-
默认情况下,const变量只在当前文件中有效,如果在多个文件中定义同一个const变量,等于分别在不同文件中定义了不同的独立const变量;
-
如果想在不同文件之间共享const变量,需要在const变量定义之前添加extern关键字;
extern const int bufSize = fcn(); // bufSize常量可以被其他文件访问
1. const和引用
-
常量引用 最标准的常量引用
#include <iostream> int main(){ const int a = 1; const int &b = a; }
引用的类型必须和所引用的对象类型一致,但是有一个例外:常量引用。
i. 常量引用右边可以为任意的,非常量/字面值/一个表达式/不同类型的引用;
ii. 但是非常量是无法引用常量的;#include <iostream> int main(){ // 常量引用非常量 // 此时编译器会自动匹配 // const int temp = 1; // const int &b = temp; int a = 1; const int &b = a; // 常量引用右边可以是任何东西 const int &c = 42; // 初始化为42 const int &d = b * 2; // 初始化为表达式 // 不同类型的引用 // 编译器 // const int temp = e; // const int &f = temp; double e = 3.14; // 3.14 const int &f = e; // 3 int &f = e; // 错 没有意义 f其实是temp的别名 改变f其实是没有意义的 temp是不存在的 // c是b的别名 b是const不可改的 但是c可以改=b也可以修改 错误 int &c = b; // 错 非常量无法引用常量 }
注意的点:常量引用非常量时,不能通过改变常量的值改变非常量。
#include <iostream> int main(){ int a = 1; int &b = a; // b绑定了a const int &c = a; // c也绑定了a 但是不允许通过c改变a a = 2; // 可以改变a std::cout << a << b << c << std::endl; // 2 2 2 b = 3; // 可以改变b std::cout << a << b << c << std::endl; // 3 3 3 c = 4; // c无法改变 }
2. const和指针
-
一般来说,指针的类型必须与其所指对象的类型一致,但是允许一个指向常量的指针指向一个非常量对象。因为常量指针说明指针指向的值不变,但是指针指向的地址是可以变的。
#include <iostream> int main(){ const double a = 3.14; double *ptr = &a; // 错误 类型要一致 const double *p = &a; double b = 3.14; p = &b; // 常量指针p重新指向一个非常量对象b (这句话不是赋值操作) }
-
常量指针:const int *p 指针所指的对象是一个常量 所以指针所指的对象的值(*p)不可以改变 -> 底层const const修饰 *p,所以 *p不能修改 指针所指的地址(p)可以修改,但是指针指向的值(*p)不可以修改
#include <iostream> int main(){ int a = 1; int b = 2; const int *p = &a; p = &b; // 指针所指的地址可以修改 *p = 2; // 指针指向的值不能修改 int const *p = &a; p = &b; // 指针所指的地址可以修改 *p = 2; // 指针指向的值不能修改 }
-
指针常量:int *const p 指针本身是一个常量,所以指针本身的值是不可以修改的 -> 顶层const const修饰p, 所以p不能修改 指针指向的值(*p)可以修改,指针本身的值/所指的地址(p)不可以修改
#include <iostream> int main(){ int a = 1; int b = 2; int *const p = &a; *p = 2; // 指针指向的值可以修改 p = &b; // 指针所指的地址不能修改 }
-
叠加使用:const int *const p 指针指向的值(*p)不能修改,指针所指的地址(p)也不能修改
#include <iostream> int main(){ int a = 1; int b = 2; const int *const p = &a; *p = 2; // 指针指向的值不可以修改 p = &b; // 指针所指的地址不能修改 }
-
顶层const和底层const
顶层const:指针本身是一个常量;int *const p 更一般的任意的对象是常量也可称为顶层const
底层const:指针所指的对象是一个常量;const int *p 引用的const都是底层const:
const int &ra = 10; 对顶层const取&,就变成底层constint i = 0 int *const p1 = &i; // 顶层const const int ci = 42; // 顶层const // ci是顶层const 但是对一个顶层const取地址&相当于又加了一个底层const const int *p2 = &ci; // 底层const const int *const p3 = p2; // 前面底层const 后面顶层const // 常量引用在左侧,右边可以是任何东西 const int &r = ci; // 引用都是底层const const int &a = 1; // 底层const int &b = a; // 错
判断原则:当指向对象的拷贝操作时,顶层const不受什么影响(可以忽略不看),底层const必须一致才能成功赋值
i = ci; // 正确:ci是顶层const 直接忽略 两者都是int类型 p2 = p3; // 正确:第二个const是顶层const 直接忽略 两者都是const int*类型 int *p = p3; //错误 p3=const int* p=int* p2 = &i // 正确 常量指针重新指向一个非常量对象
引用不是对象,不能使用上面的判断条件。
i. 常量引用如果在左侧,右侧可以接任何东西(特殊的常量可以引用非常量)
ii. 非常量引用=常量(非常量引用常量),报错
iii. 非常量 = 常量引用,对(赋值操作)const int &r = ci; // 对 const int &r = i; // 对 int &r = ci; // 错 因为肯定不能通过r来修改const ci const int a = 10 const int &d = a; int f = d; // 赋值操作 不改变常量的值 所以没关系
3. 常量表达式和constexpr变量
-
常量表达式:值不会改变,且在编译过程就能得到计算结构的表达式。即类型必须是const,值是字面值常量。
const int a = 20; // a是常量表达式 const int b = a + 1; // b是常量表达式 int c = 27; // c不是常量表达式 c可变 const int sz = get_size() // sz不是 运算时才知道sz的值
-
constexpr变量: c++11允许将变量声明为constexpr类型,以便由编译器来验证变量值是否是一个常量表达式。让该变量获得在编译阶段即可计算出结果的能力。 条件:声明为constexpr的变量必须是一个常量,而且必须用常量表达式初始化;
constexpr int a = 20; // 是 constexpr int b = a + 1; // 是 constexpr int c = size(); // 只有当size()函数是一个constexpr函数时才是 int dim = 3; // 错 dim不是常量表达式 const int dim = 3; // 对 dim是常量表达式 constexpr int dim = 3; // 对 修饰普通变量 让普通变量->常量表达式 int a[dim] = {1, 2, 3};
-
constexpr仅对指针有效,对指针所指的对象无关;
五、处理类型
1. typedef类型别名
-
类型别名:给类型取一个简单、易懂的别名
-
传统方法:typedef
typedef double de, *p; // de = double, p = double*
-
c++11方法:using
using S = Scale_item; // 别名声明 S s; // 用别名声明一个对象
-
类型别名和类型名是等价的:double a = 3.14; 等价于 de a = 3.14;
-
复合类型的别名(后续补充)
2.auto类型说名符
-
c++11新标准引入auto类型说明符,可以让编译器替我们自动分析表达式的类型。
auto i = 0, *p = &i; auto a = 0, b = 3.14; // 错
-
复合类型、常量和auto(后续补充)
3.decltype类型指示符
-
c++11新标准引入decltype,作用:选择并返回操作数的数据类型,而不关系它的值。
decltype(f()) sum = x; // sum的类型就是函数f的返回类型
-
decltype、复合类型和const(后续补充)
六、自定义数据结构
- struct: 定义类,strcut Student{}; 分号不能忘记
- 尽量不要把类的定义和变量定义放在一起,分开写最好了;
- 类内部名字必须唯一,但可以和类外名字重复;
- 每个类对象都会有一份类成员的拷贝,修改一个对象的类成员,不会影响其他对象的类成员;
- c++11可以为类数据成员提供一个类内初始值;
- 头文件
头文件中一般存放那些只能被定义一次的实体:类、const和constexpr变量等;
预处理器: 确保头文件多次包含仍然能安全工作
i. #include:在指定位置用头文件中的内容代替;
ii. #define: 把变量名定义为预处理变量;
iii. #indef: 检查预处理变量是否已经被定义,如果已定义执行后续操作,直到#endef为止;