C++基础总结(一):变量和基本类型

内容:

  • 基本内置类型
  • 变量
  • 复合类型:引用、指针
  • const限定符:const的引用、指针和const、顶层const、constexpr和常量表达式
  • 处理类型:auto、decltype
资料来源:《C++ Primer》、《C++ Primer plus》、《Effective C++》、随手博客摘要。

1、算数类型

C++:算数类型
类型含义最小尺寸
bool布尔类型未定义
char字符8位
wchar_t宽字符16位
char16_tUnicode字符16位
char32_tUnicode字符16位
short短整型16位
int整型16位
long长整型32位
long long长整型64位
float单精度浮点数6位有效数字
double双精度浮点数10位有效数字
long double扩展精度浮点数10位有效数字

注意

  • 带符号类型(signed)和无符号类型(unsigned):int、short、long、long
    long都是带符号,在其前面添加unsigned即可的到无符号类型,无符号仅表示大于等于0的数。

  • 字符型被分为三类,char和signed char类型不一样,8比特signed char理论上表示为-127至127区间内的值(大多数现代计算机为-128至127),8比特unsigned char则表示为0-255。

了解climits头文件

#include<iostream>
#include<climits>
#include<locale>
using namespace std;
int main()
{
	//wchar_t为宽字符,常用在国际程序,可以避免文件在不同平台上出现乱码的情况
	setlocale(LC_ALL, "chs");	//使用setlocale函数将本机的语言设置为中文简体
	wchar_t wt[] = L"中国";		//LC_ALL表示设置所有的选项(包括金融货币、小数点,时间日期格式、语言字符串的使用习惯等),chs表示中文简体

	int n_int = INT_MAX;		// INT_MAX:在climits头文件当中定义的预处理方式,表示最大数值
	short n_short = SHRT_MAX;
	long n_long = LONG_MAX;
	long long n_llong = LLONG_MAX;


	cout << "wchar_t is " << sizeof (wchar_t) << " bytes." << endl;		//所占字节
	cout << "int is " << sizeof (int) << " bytes." << endl;		
	cout << "short is " << sizeof n_short << " bytes." << endl;
	cout << "long is " << sizeof n_long << " bytes." << endl;
	cout << "long long is " << sizeof n_llong << " bytes.\n" << endl;

	cout << "Maximum values: " << endl;		//最大数值
	wcout << "wchar_t: " << wt << endl;		//wchar_t类型需要用wcout输出,不然只能输出地址
	cout << "int: " << n_int << endl;
	cout << "short: " << n_short << endl;
	cout << "long: " << n_long << endl;
	cout << "long long: " << n_llong <<"\n"<< endl;

	cout << "Minimum int value = " << INT_MIN << endl;		//int最小值
	cout << "Bits per byte = " << CHAR_BIT <<"\n"<< endl;	//字节占位
	system("pause");
	return 0;
}

运行结果:
结果图

1.1命名规则

  1. 名称中只能使用字母字符、数字和下划线(_)
  2. 名称的第一个字符不能为数字
  3. 区分大小写字符
  4. 不能用C++关键字作名称
  5. 尽量不要使用双下划线(如_time_stop)命名,编译执行可能会导致不确定性,不出现编译错误原因,但做法不非法
  6. C++对名称长度无限制,名称所有字符都有意义(尽量使用英文或易懂的缩写命名,可读性增加),但有些平台有长度限制。

建议:如何选择类型

  1. 当明确知晓数值不可能为负时,选用无符号类型。
  2. 使用int执行整数运算。short太小,而long和int有一样的大小,超过int就使用long long。
  3. 算数表达式不要使用char或bool,只有在存放字符或布尔值时才使用它们。算数表达式用char会很容易出现错误(符号问题)。如果必须使用,那么就明确指定类型为signed char或者unsigned char。
  4. 执行浮点数运算选用double,因为float精度不够而且双精度运算代价相差不多。对于某些机器来说双精度运算速度比单精度快。

1.2 类型转换

定义包含数据和能参与的运算,类型转换顾名思义就是将对象从一种类型转换成另一种类型。

//按值传递,提供了它的数的副本,也就是拷贝
bool b = 42;		// b 为真(true)
int i = b;				// i 的值为1
i = 3.14;				// i 的值为3
double pi = i;		// pi 的值为3.0,近似处理
unsigned char c = -1;			//假设 char 占8比特,c的值为255   (0至255),超出范围时,结果是无符号类型表示数值总数取模后的余数。
signed char c2 = 256;		//假设char占8比特,c2的值是未定义的  (-128至127),给带符号类型传递超出表示范围的值时,结果是未定义的,程序可能继续工作、崩溃、生成垃圾数据。
/*int类型数据所占内存空间为32位。
其中有符号整型变量取值范围为-2147483648~2147483647,
无符号型整型变量取值范围为0~4294967295U.
*/
unsigned u = 10;
int i = -42;
std::cout<< i + i << std::endl;			//输出-84
std::cout<< u + i <<std::endl;		//如果int占32位,输出的值为4294967264。一般我们自己计算机环境中的int占位是32位,所以u+i=-32,再将负数加上无符号数的值再取模则为(2^32-32)=4294967264,      2^32=4294967296。

2、变量

  • 变量提供一个具名的、可供程序操作的存储空间。
  • 每个变量都有其数据结构,决定变量所占内存空间大小和布局方式、该空间嫩存储的值得范围,以及变量能参与的运算
  • 变量和对象可以互换使用。对象是指一块能存储数据并具有某种类型的内存空间。

2.1初始值

//定义并赋值,随后初始化b
double a=1,b=a*2;
//调用函数d,用函数返回值初始化c
double c=d(a,b)
  1. 初始化不是赋值,初始化是创建变量给定一个初始值
  2. 赋值是把当前的值擦除,用新值代替

2.2变量声明和定义的关系

extern int i;	//声明i,未定义i
int j;			//声明并定义j
extern double pi=3.1416;	//定义pi
  • 声明是规定类型和名字
  • 定义除了声明的功能,还申请了存储空间,增加了对内存的使用
  • 声明多用在分离式编译,比如头文件跟函数的书写上

3、复合类型

  • 引用、指针
  • 处理类型:类型别名、auto类型说明符、decltype类型说明符

3.1.1 引用

  • 接下来介绍的引用都是左值引用,右值引用在后面的类部分介绍
  • 引用即别名,引用并不是一个对象,而是给一个已经存在的对象起另外一个名字。定义引用时,程序会把引用和它的初始值绑定在一起
  • 引用只能定义一次,之后不能绑定其他对象了(一次性),只能改变对象的值,且不能为空,使用时不需要解引用(*)
  • 引用不需要分配内存空间,直接使用被引用对象的内存
int i = 1024;
int &j = i;		//allowed
int &i2;		//not allowed,uninitialized
  • 引用必须要与对象使用相同类型,但是有两个例外可以使用,一个是可被类型转换的常量(第二个例外是在派生类向基类的引用中会用到),例如:
double j = 20;
const int &i=j;		//i是j的别名

上面的过程是允许的,为了了解这个过程我们可以看一下编译器是怎么使他们成功的:
const int temp = j;	      //先让双精度的j生成一个临时的整型常量
const int &i = j;		  //让i绑定这个临时量

以下为错误例子:

int i = 20;		
  
double &j = i;		//错误:引用必须使用相同类型 
double &j = 10;   //错误:引用类型的初始值必须是一个对象

3.1.2 指针

  • 与引用类似,指针可以实现对其他对象的间接访问
  • 指针是一个实体是一个对象,允许对指针的赋值和拷贝,可以指向几个不同的对象
  • 指针的赋值需要理解内存对野指针的理解,编程不像人,如果让一个指针指向数字,而不是内存地址,编译就会失败,但是使用类型转换指向一个内存就不一样了
  • 野指针会指向一个已删除的对象或者访问受限的内存区域,与空指针不一样会明确NULL,不指向任何内存,避免程序出现崩溃
  • 必须必须在指针解引用之前,将指针指向一个明确的、适当的地址
long *p3 = (long *)352;  	    //表示p3指向编号为352的内存.如果是在保护模式下,这里不允许放入数据.
long *p3 =new long;  *p=352;   //表示在堆上存储整数352
long* p3; *p3 =352; 		   //错误.往一个非法随机内存处存入数据.不过编译能过.
  • 指针在定义时可以不赋初值,但会拥有一个不确定的值
  • 当编译器访问未初始化的指针时,会出现无法预计的错误,所以使用指针时需要初始化全部指针

指针的基本用法:

1. 获取对象的地址

int i = 2;
int *p = &i;   //在表达式的右值当中,&是取地址符

如果想让指针指向引用,是不正确的,因为引用不是对象,没有实际地址,所以不能定义指向引用的指针

2. 利用指针访问对象

int i = 2;
int *p = &i;   //在表达式的右值当中,&是取地址符
cout<<*p;	   //输出2,当想要访问指针指向的对象时,需要用(*)解引用访问所指对象i
cout<<p;		//输出地址号
*p = 0;			//给解引用的指针赋值,就是给指向的对象i赋值,即i=0
cout<<*p;		//输出0

	* 解引用操作仅适用于那些确实指向某个对象的有效指针

3. 空指针
空指针不指向任何对象,在用到cstdlib头文件时,使用预处理变量NULL来给指针赋值时,指针值为0。
建议初始化所有指针,可以用对象、数值、nullptr或者NULL等初始化。如果访问了未初始化的指针,会发生意想不到的错误。

int *p1 = nullptr;	//等价于int *p = 0;
int *p2 = 0;		//直接初始化为字面常量0
int *p3 = NULL;		//需要先添加头文件cstdlib,等价于int *p3 = 0; 推荐使用新标准当中的nullptr,尽量避免使用NULL

int *p;
int zero = 0;
p = zero;	//错误:不能把int变量直接赋给指针,int *不能转换为int
p = &zero;	

4. 赋值和指针

  • 给指针赋值,就是令它存放一个新的地址
  • 从而指向一个新的对象 而给指针赋值的任务则交给&
  • 也可以用new运算符返回未命名的内存的地址,并且用delete释放内存,但是仅限于用new寻找地址的指针,并且对空指针也是有效的,可以提高内存的利用率,减少内存泄漏的危险
double *pn;		//如果pn定义在块内,则pn的值无法确定,是危险的指针
double *pa;
char *pc;
double b=3.2;
pn=&b;			//pn的值被改变,pn指向了b
pc=new char;	//寻找一个适合的内存地址赋给pc
delete pc;		//释放内存
pa=new double;
pa=pn;			//pa与pn共同指向b
*pa=0;			//指针pa指向b,解引用后把b的值设置为了0,但是指针pa不变

5.其他指针操作
只要指针是存在的、有意义的,就可以用在条件表达式中。合法的指针(不为零或地址安全),条件为true。指针值为0则为false。

int i = 1024;
int *pi = 0;
int *pi2 = &i;
if(pi)			//pi值为0,false
if(pi2)		//pi2指向i,true

6.void(*) 指针
void指针可用于存放任意对象的地址,遗憾的是我们不能直接操作void*指针所指的对象,因为并不知道指针指向的对象是什么类型,就无法确定在对象上做哪些操作,在类方面会更加详细的了解这个特殊的指针

double obj = 3.14;
double*pd = &obj;		
void *pv = &obj;	
pv = pd;

关键概念:(&)与(*),既能用作表达式里的运算符,也能作为声明的一部分。主要区分在于等号左右值以及类型位置的区分

int i = 2;			
int &r = i;			//由于在类型名后出现,则是声明r是一个引用
int *p;				//由于是类型名后出现,则是声明*是一个指针
p = &i;				//由于在表达式中出现,则是取地址符,等价于*p=i;
*p = i;				//由于在表达式中出现,则是解引用符,未初始化会出错
int &r2 = *p; 		//&为引用,*为解引用

4、const限定符

  • 可使变量定义后,变量的值不被改变,const修饰的变量必须先初始化
  • 但是在内置和POD类型内,可以不初始化,这个总结会在往后
  • 比起用宏定义#define,我们更趋向于使用const,因为可以减少编译时间
  • #define是字符替换,没有类型区别,没有类型安全检查,宏定义后的数据不属于变量,容易出现边际效应等错误
#define N 2+3;   
我们预想的N值是5
int a=N/2;
我们预想的a的值是2,可实际上a的值是3
原因在于在预处理阶段,
编译器将a=N/2处理成了a=2+3/2;
这就是宏定义的字符串替换的“边缘效应”因此要如下定义:#defineN(2+3)const定义表达式没有上述问题。
  • 被const修饰的变量,会变成只读型的变量,会在编译过程进行安全检查
  • const常量需要进行内存分配,存储与程序的数据段中。而宏定义并不需要占用内存,存储在代码段当中,需要时直接替换目标对象的数据。
const 类名 对象名
const int a = 10;   //allowed

类名 const 对象名
int const a = 10;	//allowed
int b = a;			//拷贝允许的,拷贝的对象并不会被改变
const int a;		//not allowed,uninitialized

4.1 const的引用

  • 把引用绑定到const对象上,就是常量引用
  • 被常量引用绑定的对象,不能通过常量引用修改其对象
  • 引用不能指向常量,因为引用改变了常量自身的内容,为const提供了新的别名,但是const不允许这种修改
const int i =1024;
const int &j = i;
j=42;			//not j是常量,不允许修改
int &j2 = i;	//试图让非常量引用j2指向常量i,增加常量的变量别名

4.1.1初始化和对const的引用

int i = 42;
const int &j1 = i;
const int &j2 = 42;
const int &j3 = j1 * 2;
int &j4 = j1 * 2;			//错误,常量引用不允许绑定到非常量引用上

1、 int常量引用可绑定到double非常量引用上,这个例子在引用上已经提及

double a = 4.33;
const int &b = a;	//编译器生成了temp临时常量等于4.33,让b绑定在了temp上

2、非常量被常量引用绑定,可以依靠其他途径修改非常量

被const常量绑定的非常量对象,可以绑定非常量引用,并且依靠非常量引用修改非常量对象的值

int i = 42;			
int &r1 = i;
const int &r2 = i;		//i有了r1,r2的别名
r1 = 0;					//可以利用非常量引用修改i的值
r2 = 0;					//不可以利用常量引用修改i的值

4.2 指针和const

在了解指针和const的关系时,我们必须理解指向常量的指针、常量指针和指向常量的常量指针是什么意思

int j = 42;
const int *i = &j;		//指向常量的指针,可以更换指向的对象,但是不能通过指针改变指向的对象的值
int *const i = &j;		//常量指针,也可说是常指针,永远指向j,但可以通过指针修改指向的对象的值
const int *const i = &j;	//指向常量的常量指针,不可修改指针指向,并不可通过指针修改指向的对象的值
  • 普通指针不可以指向常量
  • 指向常量的指针指向常量后,不可以依靠指针改变常量
  • 指向常量的指针可以指向非常量对象,前提是类型一致
const double pi = 3.14;
double *ptr = &pi;			//not ptr是普通指针
const double *cptr = &pi;
*cptr = 42;					//常量指针不能修改常量

int j = 42;
const int *j = &j;			//指向常量的指针可以指向普通变量,但是不可以通过指向常量的指针修改对象的值
int j = 2;					//但可以依靠别的方法修改变量

4.2.1 const指针

  1. 常量指针必须初始化
  2. 初始化后指针的值就不能被改变,也就是不能依靠指针去修改指向的对象的值
  3. 但是将*放在const前面就另当别论了,*const表示的意思是不变的是指针本身,可以修改对象的值

const 类型名 *const 对象名,这个的含义是指向不能变,并且不能通过此指针改变对象的值

int i = 42;
int *const j = &i;		//j将永远指向i
*j = 2;					//允许修改i的值
int p = 1;
int *const j = &p;		//不允许修改指向的对象	

const int *const ptr = &p		//永远指向p,并且不允许通过指针修改指向的对象的值
*ptr = 2;						//错误,ptr是一个指向常量的常量指针

4.2.2 顶层、底层const

  1. 顶层const:表示指针本身是个常量,例如int *const i。永远指向初始化的对象,但是指针可以修改指向的对象的值
  2. 底层const:表示指针所指的对象是一个常量const int *i。可以改变指向,但是指针不可以修改指向的对象的值
  3. 简单的来说,靠*右边的const为顶层,靠左边的为底层
  4. 过多的描述没什么意义,主要在指向方面,需要看清楚是顶层还是底层,类型也要一致,除非他们类型可以转换

4.2.3 常量表达式和constexpr

常量表达式:指值不会改变并且能在编译过程得出结果的表达式

const int max = 20;			//是常量表达式
const int num = max+1;		//是常量表达式
int size = 2;				//非常量表达式
const int s = get_size();	//非常量表达式,虽然s是常量,但是必须要等到运行才可以得出结果

constexpr变量:c++11标准规定,允许将变量声明为constexpr类型分辨一个初始值是不是常量表达式,而constexpr类型的内容,就是定义一个const,并且增加了编译器验证变量是否为常量表达式的功能

constexpr int i = 42;			//是常量表达式
constexpr int j = i+1;			//是常量表达式
constexpr int t = size();		//当size函数是一个constexpr函数时是一个常量表达式

指针和constexpr的关系:如果在constexpr的声明里定义了一个指针,则constexpr仅对指针有效,与指针所指的对象无关,也就是设置了个顶层const

constexpr int *p = nullptr;		//p是一个指向整数的常量指针,顶层const
const int *q = nullptr;			//q是一个指向整型常量的指针,底层const

const int i = 42;
constexpr const int *d = &i;	//d是一个指向整数常量的常量指针

要注意的是,使用constexpr

5、处理类型

在后面的学习中,类型往往很难记,而且容易记错,这就需要用到一些工具处理了。

5.1.1 类型别名

  • 类型别名是个名字,它是某种类型的名字(同义词)
  • 可以使复杂的类型名字变的简单明了、易于理解和使用
  • 定义类型别名有两种方法,一种是使用关键字typedef,另一种是新标准规定的方法,使用别名声明关键字using
typedef double a;		//a设置为double的同义词
typedef a b,*p;			//b是double的同义词,*p是double *的同义词

//MyInVector是std::vector<int,MyAlloc<int>>类型的同义词
using MyIntVector = std::vector<int, MyAlloc<int>>;

5.1.2 指针、常量和类型别名

typedef char*陷阱:const与typedef一起出现时,typedef不是简单的字符串替换
在下面的例子中有个需要注意的点,当const跟cptr共用时,他的含义是char * const,而不是我们想当然的const char *,原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。
简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行

typedef char *cptr;		//cptr是char *的同义词
const cptr cp = 0;		//cp是指向char的常量指针,等价于char *const cp = 0;
const cptr *p;			//p是一个指向char的常量指针,等价于char *const *p;

5.2 auto类型说明符

  • auto的作用是代替我们去自动识别表达式的类型
  • 它是通过识别初始值来判断类型的,所以使用auto时必须初始化
  • 在同一条定义里面定义多个变量时,他们的类型要相同
auto i = 4;
auto l = 2;
auto j = i + l;		//j初始化为i与l的和 

auto i = 0,*p = &i;		//正确,i是整数,p是整型指针
auto s = 0, pi = 3.14;	//错误,s与pi的类型不一致

5.2.1 复合类型、常量和auto

  • 编译器推断出来的auto类型有时候与初始值的类型不一样,编译器会适当地改变结果类型使其更符合初始化规则,比如auto一般会忽略顶层const,而底层const会被保留下来
  • 在auto当中如果将等式右边取地址符,其实就是指针类型,因为一个值的地址,就是指向这个类型的值的指针
int i = 0,&r = i;
auto a = r;			//a是一个整数类型,r是i的别名,i是一个整数

const int ci = i,&cr = ci;
auto b = ci;		//b是一个整数(ci的顶层特性被忽略)
auto c = cr;		//c是一个整数(cr是ci的别名,ci的顶层特性被忽略)
auto d = &i;		//d是一个整型指针(i是普通整型,整型取地址就是指向整数的指针)
auto e = &ci;		//e是一个指向整数常量的指针(ci是一个常量整数,对常量对象取地址是一种底层const)
  • 如果希望auto类型是一个顶层const,则需要明确指出:
const auto f = ci;		//ci的顶层特性被忽略,识别为int ci,f即为const int
  • 当auto一个引用时,就不会出现上面的现象
auto &g = ci;		//g是一个整形常量引用,绑定ci,g为ci别名
auto &h = 42;		//错误,不能为非常量引用绑定字面值
const auto &j = 42;	//正确
  • 要在一条语句中定义多个变量,符号&和*知识属于声明符,并不是数据类型的一部分,初始值需要同一类
auto k = ci,&l = i;	//k是一个整数,l是一个整型引用
auto &m = ci,*p = &ci;	//m是对整型常量的引用,p是一个指向整型常量的指针

auto &n = i,*p = &ci;	//错误,同一条定义语句,i是int,而&ci是const int,不能放在同一条auto中

5.2.2 decltype类型指示符

  • 当不想初始化变量,又希望从表达式的类型推断出要定义的变量的类型,c++11新标准引入了新的类型说明符decltype
  • decltype的作用是选择并返回操作数的数据类型,编译器会分析表达式并得到类型,却不去计算其实际值
  • decltype处理顶层const和引用的方式与auto有些许不同,比如不会忽视顶层const
decltype (f()) sum = x;		//sum的类型就是f函数的返回类型

const int ci = 0,&cj = ci;		
decltype(ci) x = 0;			//x的类型是const int
decltype(cj) y = x;			//y的类型是const int&,y是x的别名
decltype(cj) z;				//错误,引用必须初始化
  • decltype的括号里是非引用但在外面再加一层括号,则为引用:decltype((d)),d为int,推断后为int&
int i = 1;
decltype(i) a;		//正确,a为int
decltype((i)) b;	//错误,b为引用必须初始化
  • 当表达式不为变量,则decltype返回表达式结果对应的类型
int i = 42;
int *P = &i;	//p指向i
int &r=i;		//r是i的别名
decltype(r+1) b;		//正确,由于括号内不为变量,返回他们表达式结果的类型,b为int
decltype(*p) c;			//错误,*为解引用符号不为变量,解引用后c为int&,必须初始化

特别要注意的是,如果双括号,则结果永远为引用。单括号只有里面的值为引用,结果才为引用。

小结:

这些内容大部分都来自《C++ Primer》,总体而言还算简单易懂,因为这些都是基础知识,在往后的函数还有容器等内容中,这些知识是必不可少的,学习过程中也会进行一些扩展知识的填充,写总结的用意是为了能够增强对这些内容的印象,偶尔翻一下查看以前写的渣渣博客有什么错误,并且对其加以改正或者补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值