这个应该是C++基础知识最后的一小部分,前面我们们说了这么多,都是为了之后的类和对象做铺垫,今天我们主要看看两个最基本的知识。
这个关键字我们在C语言的时侯也专门和大家分享过,在C语言里我们都是忽略了这个关键字,不过在C++里面,这个关键字又被赋予的新的特性。
在早期C/C++中auto的含义是:使用auto 修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有
人去使用它.在C++11中,标准委员会赋予了auto全新的含义即:**auto **不再是一个存储类型指示符,而是作为一个新的类型
指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得
说人话就是auto可以根据我们所给的数据自动推出他应该是什么类型.这一点很重要.
#include
using namespace std;
int main()
{
auto x = 1;
auto ch = 'c';
return 0;
}
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
//typeid 可以测试 变量的类型
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
好了,到这里auto的最基本的点已经说完了,大家可能会感到疑惑,我们分明可以定义数据的类型,为何还要auto,这不是多此一举吗?
这个问题很好,如果是在C语言中,这个功能确实是鸡肋,但是在C++中,如果你学了模板,就会发现这个auto实在是太好用了,我先和大家演示一下,下面的代码看不懂不要紧,后面我们都会去认识的,这里就是为了举例子.
看看下面的代码.
int main()
{
std::map<std::string, std::string> dict;
dict["sort"] = "排序";
dict["string"] = "字符串";
std::map<std::string, std::string>::iterator it = dict.begin();
return 0;
}
变量的类型实在是太长了,但是我们是使用auto就不一样了,编译器会自动推道.
int main()
{
std::map<std::string, std::string> dict;
dict["sort"] = "排序";
dict["string"] = "字符串";
//std::map::iterator it = dict.begin();
auto it = dict.begin();
return 0;
}
我们已经知道了auto的基本用法了,现在可以看看它的一些特性了.
auto和引用一样,我们必须给auto修饰的变量初始化,否则无法编译.
int main()
{
auto a;
return 0;
}
auto可以在同一行定义多个变量,但是这些变量的类型必须是一样的否则就会出现报错.
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对
第一个类型进行推导,然后用推导出来的类型定义其他变量
int main()
{
auto a = 1,b = 2;
auto c = 1.0,b = 'c'; // 报错
return 0;
}
我们来看看这两种的使用方法.
这个我必须和大家要仔细的分析下,auto可以推导指针类型,这一点毋庸置疑.
int main()
{
int a = 10;
auto pa = &a;
cout << typeid(pa).name() << endl;
return 0;
}
但是你看看下面的代码是不是正确的?
int main()
{
int a = 10;
auto* pa = &a; //加了一个 *
cout << typeid(pa).name() << endl;
return 0;
}
通过结果我们看出来,加不加* 的作用都是一样的,大家用的时候选择哪个都可以的.
这里说一下,auto不能自动的推导出来该类型的引用,也就是说我们必须帮助编译器识别.
int main()
{
int a = 10;
auto& pa = a;
cout << "&a: " << &a << endl;
cout << "&pa: " << &pa << endl;
return 0;
}
auto也不是万能的,我们不能把所有的期望都寄托到编译器身上,auto有下面几种不能推导的地方
我们在打印一个数组的时候,应该怎么办?我们来尝试一下.
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
但是在C++11标准出来后,就可以使用范围for,这种方法很简单.
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int val : arr)
{
cout << val << " ";
}
cout << endl;
return 0;
}
现在我们还不能完全认识它的原理,只有到后面的迭代器才可以,现在我们就简单的谈谈它的原理.
编译器为 val 开辟一块空间,自动获取数组arr里面的元素,把他的值赋给val,直到编译器觉得数组已经打印完了.
for (auto val : arr) //auto也可以在这里使用
{
cout << *val << " ";
}
我们使用的范围for,也是存在一定的问题的,你会发现,他一打印就是打印整个数组,不会停下来.也就是说,编译器控制这整个打印流程.这就给我们加下来的打印造成了很大的困难.
范围for不能打印作为参数的数组,编译器找不到何时可以开始和停止的位置.
void func(int* arr)
{
for (int val : arr)
{
cout << val << " ";
}
cout << endl;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
func();
return 0;
}
nullptr 和我们C语言里面的NULL是一样的,都是空指针.但是C++里面的NULL和nullptr 可不一样.我们现在来看看C++里面的NULL
现在我们就要看看会调用哪个func函数.
void func(int* p)
{
cout << "p是指针" << endl;
}
void func(int p)
{
cout << "p是整型" << endl;
}
int main()
{
func(NULL);
return 0;
}
我们蒙了,NULL不是空指针吗?为何会调用第二个函数?这不是有点荒唐吗?
NULL本质上是一个宏,我们看看C++关于NULL宏定义.
如果没有定义NULL,如果存在 __cplusplus这个宏(C++特有的),那就把数字0定义成NULL,否则就把地址0定义给NULL.也就是说C++里面的NULL就是数字0.nullptr 是一个关键字,它的原理是0地址.
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
int main()
{
func(nullptr);
return 0;
}