C++相关基础了解

C++相关基础特性了解

空指针

​ 空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。

以下列出几个生成空指针的方法:

int *p1 = nullptr;	// 等价于int *p1 = 0
int *p2 = 0;	// 直接将p2初始化为字面常量0
//	需要首先#include<cstdlib>
int *p3 = NULL; // 等价于int *p3 = 0;

得到空指针最直接的方法就是用字面值nullptr来初始化指针,这也是C++11新标准刚刚引入的一种方法。nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。

注:NULL预处理变量 — 位于头文件cstdlib

过去的程序会用到一个名为NULL的预处理变量(preprocesspr variable)来给指针赋值,这个变量在头文件cstdlib中定义,它的值就是0

小知识点:预处理器是运行于编译过程之前的一段程序

​ 预处理变量不属于命名空间std,它由预处理器负责管理,因此我们可以直接使用预处理变量而无须在前面加上std::

当用到一个预处理变量时,预处理器会自动地将它替换为实际值,因此用NULL初始化指针和0初始化指针是一样的。在新标准下,现在的C++程序最好使用nullptr,同时避免使用NULL

​ 注意事项:把int变量直接赋给指针是错误的操作,即使int变量恰好等于0也不行

int zero = 0;
int *p = zero;	// 错误:不能把int变量直接赋给指针
建议:初始化所有指针(使用初始化的指针)

​ 使用未经初始化的指针是引发运行时错误的一大原因。

​ 和其他变量一样,访问未经初始化的指针所引发的后果也是无法预计的。通常这一行为将造成程序崩溃,而且一旦崩溃,想要定位到出错位置将是特别麻烦的问题。

​ 在大多数编译器环境下,如果使用了未经初始化的指针,则该指针所占内存空间的当前内容将被看作一个地址值。访问该指针,相当于去访问一个本不存在的位置上的本不存在的对象。糟糕的是,如果指针所占内存空间中恰好有内容,而这些内容又被当作了某个地址,我们就很难分清它到底是合法还是非法的了。

​ **因此建议初始化所有的指针,并在可能的情况下,尽量等定义了对象之后再定义指向它的指针。**如果实在不清楚指针应该指向何处,就把它初始化为nullptr或者0,这样程序就能检测并知道它没有指向任何具体的对象了。

处理类型

1.类型别名

类型别名(type alias)是一个名字,它是某种类型的同义词。使用类型别名有很多好处,它让复杂的类型名字变得简单明了,易于理解和使用,还有助于程序员清楚地知道使用改类型的真实目的

有两种方法可以用于定义类型别名。

传统的方法是使用关键字typedef

typedef double dou;	// dou是double的同义词
typedef int I; // I是int的同义词
typedef long long ll;	// ll是long long的同义词

其中,关键字typedef作为声明语句中的基本数据类型的一部分出现。含有typedef的声明语句定义的不再是变量而是类型别名。和以前的声明语句一样,这里的声明符也可以包含类型修饰,从而也能由基本数据类型构造出复合类型来。

新标准规定了一种新的方法,使用别名声明(alias declaration)来定义类型的别名:

using stu = Student;	// stu是Student的同义词

这种方法用关键字 using 作为别名声明的开始,其后紧跟别名和等号,其作用是把等号左侧的名字规定成等号右侧类型的别名

类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名:

dou price,salary;	// 等价于 double price,salary;
stu s1;	// 等价于 Student s1;
2.auto类型说明符

结论:(auto定义的变量必须有初识值)

​ 编程时我们常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型。但是,要做到这一点并非那么容易,有时可能根本做不到

C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。和原来那些只对应一种特定类型的说明符(比如double)不同,auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值

// 由val1和val2相加的结果可以推断出item的类型
auto item = val1 + val2;	// item初始化为val1和val2相加的结果

此处编译器将根据 val1 和 val2 相加的结果来推断item的类型。

​ 使用auto也能在一条语句中声明多个变量。因为一条语句只能有一个基本数据类型,所以改语句中所有变量的初始基本数据类型都必须一样:

auto i = 0,*p = &i;	// 正确:i是整数,p是整型指针
auto sz = 0,pi = 3.14;	// 错误:sz和pi的类型不一致
3.decltype类型指示符(仅作了解)

少数情况:希望从表达式的类型推断出要定义的变量的类型,但是不想用表达式的值初始化变量。

C++11新标准引入了第二章类型说明符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值:

#include<iostream>
using namespace std;

int abs(int a) {
	return (a>=0) ? a:-a;
}

int main()
{
	decltype(abs(-5)) val = 5;
	cout<<val<<endl;
	return 0;
}
4.编写头文件

​ 头文件通常包含那些只能被定义一次的实体,比如类,const 和 constexpr 变量等。头文件也经常用到其他头文件的功能。

注:头文件一旦改变,相关的源文件必须重新编译以获取更新过的声明

预处理器概述

​ 确保头文件多次包含仍然能安全工作的常用技术是预处理器(preprocessor),它由C++语言从C语言继承而来。预处理器是在编译之前执行的一段程序 ,可以部分地改变我们所写的程序。之前已经用到了一项预处理功能#include,当预处理器看到#include标记时就会用指定的头文件的内容代替#include

​ C++程序还会用到一项预处理功能的是头文件保护符(header guard),头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。

#define 指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:#ifdef 当且仅当变量已定义时为真,#ifndef 当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到 #endif 指令为止

​ 而使用这些功能就能有效地防止重复包含的发生:

#ifndef STUDENT_H
#define STUDENT_H
#include<string>

struct Student { // 结构体定义形式 
	Student() =default;
	std::string name;
	unsigned height; 
};

class student { // 类的定义形式 
	public:
		student() =default;
		~student() =default;
	private:
		std::string name;
		unsigned height;
}; 

#endif

注意:预处理变量无视C++语言中关于作用域的规则

整个程序中的预处理变量包括头文件保护符必须唯一,通常的做法是基于头文件中类的名字来构建保护符的名字,以确保其唯一性。为了避免与程序中的其他实体发生名字冲突,一般把预处理变量的名字全部大写

建议:头文件即使(目前还)没有被包含在任何其他头文件中,也应该设置保护符。头文件保护符很简单,程序员只要习惯性地加上就可以了,没有必要太在乎你的程序到底需要不需要

标准库initializer_list类型(含有可变形参)- 拓展内容

​ 为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板,即tuple类型

initializer_list形参

如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_list类型的形参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。initializer_list类型定义在同名的头文件中。

注:initializer_list类型形参——函数的实参数量未知但全部形参类型都相同

initializer_list提供的操作:

操作解释
initializer_list< T > lst;默认初始化:T类型元素的空列表
initializer_list< T > lst{a,b,c…}lst的元素数量和初始值一样多;lst的元素对应初始值的副本;列表中的元素是const
lst2(lst)
lst2 = lst
拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素
lst.size()列表中的元素数量
lst.begin()返回指向lst中首元素的指针
lst.end()返回指向lst中尾元素下一位置的指针

​ 和vector一样,initializer_list也是一种模板类型。定义initializer_list对象时,必须说明列表中所含元素的类型:

initializer_list<string> ls;	//	initializer_list的元素类型是string
initializer_list<int> li;	//	initializer_list的元素类型是int

但,和vector不一样的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值!!!

​ 例如:我们使用如下的形式编写输出错误信息的函数,使其可以作用于可变数量的实参:

void error_msg(initializer_list<string> il) {
	for(auto beg=il.begin();beg!=il.end();++beg)
		cout<<*beg<<ends;
	cout<<endl;
}

​ 完整程序代码:

#include<iostream>
#include<initializer_list>
#include<string>
using namespace std;

void error_msg(initializer_list<string> il) {
	for(auto beg=il.begin();beg!=il.end();++beg)
		cout<<*beg<<ends;
	cout<<endl;
}

int main()
{
	error_msg({"run_error","Memory_leak"});
	return 0;
}

**练习:**编写一个程序,它的参数是initializer_list< int >类型的对象,函数的功能是计算列表中所有元素的和

#include<iostream>
#include<initializer_list>
#include<string>
using namespace std;

int iCount(initializer_list<int> il) {
	int count = 0;
	// 遍历il的每一个元素
	for(auto val:il)
		count += val;
	return count; 
}

int main() 
{
	// 使用列表初始化的方式构建initializer_list<int>对象
	// 然后把他作为实参传递给函数iCount
	cout<<"1,6,9的和:"<<iCount({1,6,9})<<endl;
	cout<<"4,5,9,18的和:"<<iCount({4,5,9,18})<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值