【C++】《C++ Primer》第二章:变量和基本类型( 知识点总结)

目录

2.1 基本内置类型

关于int和long

有符号数和无符号数

八进制和十六进制的表示

2.2 变量

初始化和赋值的区别

列表初始化

初始化的注意事项

编程习惯!

声明和定义

一条语句声明多个变量

静态类型

2.3 复合类型

引用和初始化的关系

引用和变量地址的关系

合法&不合法的引用

引用和指针的符号说明

空指针 nullptr和NULL

编程习惯!

赋值和指针、函数内和函数外的区别

关于变量声明写法的一些容易误导的地方

2.4 const 限定符

初始化和有效范围

const的初始化和引用

const指针

易混淆

易错例子

常量表达式

constexpr和指针

2.5 处理类型

using类型别名

auto

decltype

2.6 自定义数据结构

预处理器概述

编程习惯!


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的初始化方式,不是初始化列表,初始化列表是构造函数里面用到的一个概念。

举例:

注意:如果初始化的时候存在信息丢失的风险,那么无法通过编译!

ddef69637ad5b6093949d8c81b9ebcc0.png

因为这样会丢失小数点后面的数,从而初始化错误!

通过编译,输出是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,现在可以通过pa=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的初始化:必须是nullptr0或存储于某个固定地址的对象。

注:

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/#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

 

编程习惯!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值