这篇文章主要对c++的学习做一个基础铺垫,方便后续学习。主要通过示例讲解命名空间、c++的输入输出cout\cin,缺省参数、函数重载、引用、内联函数,auto关键字,for循环,nullptr以及涉及到的周边知识,面试题等。为后续类和对象学习打基础。
目录
什么是c++?
C++关键字(C++98)
一、命名空间
命名空间定义
1.正常的命名空间的定义
域:
命名空间域
展开命名空间
std:所有C++库命名空间
命名空间可以嵌套
二、C++输入&输出
<<:
>>:
std命名空间的使用惯例:
三、缺省参数
缺省/默认参数:
全缺省(给出所有参数):
半缺省(从右往左连续给):
注意:
四、函数重载
C++支持函数重载的原理--名字修饰(name Mangling)
*有关于预处理、编译、汇编、链接的详解,可见:
五、引用(别名)
引用概念
使用方法:
引用特性:
1.引用必须初始化
编辑
2.引用定义后,不能改变指向
3.一个变量可以有多个引用,多个别名
常引用:只能权限缩小,不能权限扩大
使用场景:
1、做参数(a、输出型参数 b、对象比较大,减少拷贝,提高效率) 这些效果,指针也可以,但是引用更方便
a、输出型参数
b、对象比较大,减少拷贝,提高效率
2、做返回值(a、修改返回对象 b、减少拷贝提高效率)
面试题:引用和指针的区别:
六、内联函数
面试题:宏的优缺点
内联函数概念:
查看方式:
编辑inline 特性
inline是一种以空间换时间的做法
内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求:
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。
为什么要防止链接冲突?
在.h未做声明和定义分离时,为什么会出现这样的报错?
以下有三种解决方案:
1、当然就是声明和定义的分离
2、加静态static,修饰函数时,与全局函数相比,有链接属性,只在当前文件可见。(不会进符号表)
3、使用内联(内联也不支持声明与定义分离)
总结:若需在.h中定义函数:
七、auto关键字(C++11)
auto简介
auto使用示例:
auto不能推导的场景
八、基于范围的for循环(C++11)
范围for的语法
范围for的使用条件
九、指针空值nullptr(C++11)
注意:
什么是c++?
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的 程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机 界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。 1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一 种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而 产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的 程序设计,还可以进行面向对象的程序设计。
C++关键字(C++98)
C++总计63个关键字,C语言32个关键字。
以下是学习c++时会用到的关键字。
一、命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
1.正常的命名空间的定义
域:
::域作用限定符
编译器搜索原则:
不指定域:
首先在当前局部域搜索再到全局域。
指定域:
如果指定了域,直接去指定域搜索
示例:
#include<stdio.h>int x = 0;int main() {int x = 1;printf("hello world\n");printf("%d\n", x);printf("%d\n", ::x);return 0; }
输出结果:
hello world
1
0
命名空间域
示例:
proj1是命名空间的名字,一般开发中是用项目名字做命名空间名。
命名空间中可以定义变量/函数/类型
#include<stdio.h> #include<stdlib.h>namespace proj1 {int rand = 0;int x = 0;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;}; }namespace proj2 {int x = 1; }
int main() {printf("%d\n", proj1::x);printf("%d\n", proj2::x);printf("%p\n", rand);printf("%d\n", proj1::rand);printf("%d\n", proj1::Add(1,2));return 0; }
输出结果:
0
1
00007FFD91E94AD0
0
3
展开命名空间
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
示例:
Queue.h
namespace xxx {struct Node{int val;struct Node* next;};struct Queue{struct Node* head;struct Node* tail;int size;};void Init(struct Queue* pq);void Push(struct Queue* pq, int x); }
List.h
#pragma oncenamespace xxx {struct QNode{int val;struct QNode* next;struct QNode* prev;};void Init(struct QNode* phead);void PushBack(struct QNode* phead, int x); }
#include<stdio.h> #include"List.h" #include"Queue.h"// 展开命名空间 using namespace xxx;int main() {//printf("hello world\n");struct QNode node1;struct xxx::QNode node2;struct xxx::QNode node3;return 0; }
此时就会通过:using namespace xjh;
到我自定义的命名空间去寻找展开
std:所有C++库命名空间
示例:
#include<iostream> using namespace std;int main() {std::cout << "hello world" << std::endl;std::cout << "hello world" << std::endl;std::cout << "hello world" << std::endl;std::cout << "hello world" << std::endl;std::cout << "hello world" << std::endl;std::cout << "hello world" << std::endl;cout << "hello world" << endl;return 0; }
命名空间可以嵌套
示例:
#include<iostream> #include<stdio.h> using std::cout; using std::endl; namespace N1 {int a;int b;int Add(int left, int right){return left + right;}namespace N2{int c;int d;int Sub(int left, int right){return left - right;}} }int main() {int ret = N1::Add(1, 2);int sub = N1::N2::Sub(5, 6);cout << ret << endl;cout << sub << endl;}
输出结果:
3
-1
二、C++输入&输出
说明:
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含
在< iostream >头文件中。
3. 是流插入运算符,>>是流提取运算符。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入输出可以自动识别变量类型。
5. 实际上 cout 和 cin 分别是ostream和istream类型的对象,>>和也涉及运算符重载等知识,我们这里只是简单学习他们的使用。
<<:
1.左移 相当于扩大2倍
2.流插入 自动识别类型
示例:
#include<iostream> using namespace std;int main() {// 1、左移int i = 100;i = i << 1;const char* str = "hello world";char ch = '\n';2、流插入 自动识别类型cout << str << i << ch << endl;printf("%s%d %c", str,i,ch);return 0; }
输出结果:
hello world200
hello world200
>>:
右移:缩小2倍
流提取:
示例:
#include<iostream> using namespace std;int main() {int i;const char* str = "hello world";char ch;// 右移流提取cin >> i >> ch;cout << str << i << ch << endl;return 0; }
输出结果:
50 \n(这个是我自己输入的)
hello world50\
ps:关于cout和cin还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等 等。因为C++兼容C语言的用法,这些又用得不是很多,我们这里就不展开学习了。
以下提供一个例子:
#include<iostream> using namespace std;int main() {double d = 1.11111111;printf("%.2lf\n", d);cout << d << endl;return 0; }
输出结果:
1.11
1.11111
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢?
1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模 大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。
三、缺省参数
缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
这里与c语言的区别:c语言必须要和申明函数的格式相同。
缺省/默认参数:
示例:
#include<iostream> using namespace std; void Func(int a = 0) {cout << a << endl; }int main() {Func(1);Func();return 0; }
输出结果:
1
0
全缺省(给出所有参数):
示例:
#include<iostream> using namespace std;//全缺省 void Func(int a = 10, int b = 20, int c = 30) {cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl; }int main() {Func(1, 2, 3);Func(1, 2);Func(1);Func();return 0; }
输出结果:
a = 1
b = 2
c = 3a = 1
b = 2
c = 30a = 1
b = 20
c = 30a = 10
b = 20
c = 30
半缺省(从右往左连续给):
示例:
#include<iostream> using namespace std;//半缺省 从右往左连续给 void Func(int a, int b = 20, int c = 30) {cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl; }int main() {Func(1, 2, 3);Func(1, 2);Func(1);return 0; }
输出结果:
a = 1
b = 2
c = 3a = 1
b = 2
c = 30a = 1
b = 20
c = 30
注意:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给,如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
//a.hvoid Func(int a = 10);// a.cppvoid Func(int a = 20){}
2. 缺省参数不能在函数声明和定义中同时出现
3. 缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)
四、函数重载
C语言不允许同名函数
CPP语言允许同名函数,要求:函数名相同,参数不同,构成函数重载
1、参数类型的不同
2、参数个数不同
3、参数顺序不同(本质还是类型不同)示例:
#include<iostream> using namespace std;// _Z3Addii void Add(int left, int right); //{ // cout << "int Add(int left, int right)" << endl; // return left + right; //}// _Z3Adddd double Add(double left, double right); //{ // cout << "double Add(double left, double right)" << endl; // return left + right; //}void f(int a, char b); void f(char b, int a);int main() {Add(1, 2);Add(1.1, 2.2);//f(1, 'a'); // call f(?)//f('a', 1); // call f(?)// f(1, 'a'); // call _Z1fic(?)f('a', 1); // call _Z1fci(?)return 0;
C++支持函数重载的原理--名字修饰(name Mangling)
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
*有关于预处理、编译、汇编、链接的详解,可见:
Linux基础 - yum、rzsz、vim 使用与配置、gcc/g++的详细解说以及预处理、编译、汇编、链接的详细步骤ESc 、iso_linux rzsz-CSDN博客
C语言不支持重载 :
因为链接时,直接用函数名去找地址,有同名函数,区分不开
CPP如何支持的呢?
函数名修饰规则,名字中引入参数类型,各个编译器自己实现了一套
五、引用(别名)
引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
使用方法:
类型& 引用变量名(对象名) = 引用实体;
改变引用时,原值也会随之改变
示例:
#include<iostream> using namespace std;int main() {int a = 0;// 引用,b就是a的别名int& b = a;cout << &b << endl;cout << &a<< endl;b++;a++;cout << b << endl;cout << a << endl;int& c = a;int& d = c;d++;cout << d << endl;return 0; }
输出结果:
当引用做参数时,会比指针方便很多:
因为,传的就是原值的别名,因此连带原值直接修改
指针和引用功能类似,重叠,但是引用不能完全替代指针,因为引用定义后,不能改变指向
示例:对比以下两种 传的就是原值的别名,因此连带原值直接修改
#include<iostream> using namespace std;//做参数 void Swap(int* a, int *b) {int tmp = *a;*a = *b;*b = tmp; }void Swap(int& a, int &b) {int tmp = a;a = b;b = tmp; }int main() {int x = 0, y = 1;Swap(&x, &y);cout << "x = " << x << " y = " << y << endl;Swap(x, y);cout << "x = " << x << " y = " << y << endl;return 0; }
引用特性:
1.引用必须初始化
示例:
2.引用定义后,不能改变指向
示例:
C++的引用,对指针使用比较复杂的场景进行一些替换,让代码更简单易懂,但是不能完全替代指针。
引用不能完全替代指针原因:引用定义后,不能改变指向特别是当我们写一个链表时:因为引用不能改变指向,因此在这里根本不适用。
struct Node {struct Node* next;struct Node* prev;int val; };//void PushBack(struct Node* phead, int x) //{ // // phead = newnode; //}//void PushBack(struct Node** pphead, int x) //{ // // *pphead = newnode; //}void PushBack(struct Node*& phead, int x) {//phead = newnode; }int main() {struct Node* plist = NULL;return 0; }typedef struct Node {struct Node* next;struct Node* prev;int val; }LNode, *PNode;void PushBack(PNode& phead, int x) {//phead = newnode; }//void PushBack(SeqList* ps, int x); //void PushBack(SeqList& ps, int x); //{}int main() {PNode plist = NULL;return 0; }
3.一个变量可以有多个引用,多个别名
常引用:只能权限缩小,不能权限扩大
示例:
#include<iostream> using namespace std;void TestConstRef() {const int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;double d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d; }
并且可用于:函数传参时,防止传的值不能被修改
使用场景:
1、做参数(a、输出型参数 b、对象比较大,减少拷贝,提高效率)
这些效果,指针也可以,但是引用更方便
示例:
a、输出型参数
b、对象比较大,减少拷贝,提高效率
#include<iostream> #include <time.h> using namespace std; struct A { int a[10000]; }; void TestFunc1(A a) {} void TestFunc2(A& a) {} void main() {A a;// 以值作为函数参数size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作为函数参数size_t begin2 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc2(a);size_t end2 = clock();// 分别计算两个函数运行结束后的时间cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; }
输出结果:可以观察出这时候使用引用更加提高效率
2、做返回值(a、修改返回对象 b、减少拷贝提高效率)
示例:
下面代码输出结果是什么?
为什么?
int& Add(int a, int b) {int c = a + b;return c; } int main() {int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0; }
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。
面试题:引用和指针的区别:
语法:
1、引用是别名,不开空间,指针是地址,需要开空间存地址
2、引用必须初始化,指针不做必要
3、引用不能改变指向,指针可以
4、引用相对更加安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用
5、sizeof 、 ++ 、解引用访问等方面的问题:(引用是引用类型大小,指针永远都是4 or 8)
底层:
汇编层面上,没有引用,都是指针,引用编译后也转换成指针了。
如图:
六、内联函数
面试题:宏的优缺点
宏的优点:
1、增强代码的复用性
2、提高性能
c++有哪些技术替代宏?
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数
知识衍生:实现两个数相加的宏函数:
1、不是函数 | #define ADD(int a, int b) return a+b; |
2、分号 | #define ADD(a, b) a+b; |
3、括号控制优先级 | #define ADD(a, b) ((a)+(b)); |
核心点:宏是预处理阶段进行替换 |
为什么加里面的括号:
#include<iostream>
using namespace std;
#define ADD(a, b) ((a)+(b))
// 为什么要加里面的括号?
int main()
{
if (ADD(1, 2))
{}ADD(1, 2) * 3;
int x = 1, y = 2;
ADD(x | y, x & y); // (x|y + x&y) // |,&的优先级小于+return 0;
}
宏的缺点:
1、语法复杂,坑很多,不容易控制
2、不能调试
3、没有类型安全的检查
由此我们引入内联:
内联函数概念:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
示例
#include<iostream> using namespace std;inline int Add(int a, int b) {return a + b; }int main() {int ret1 = Add(1, 2) * 3;int x = 1, y = 2;int ret2 = Add(x | y, x & y);return 0; }
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不 会对代码进行优化,以下给出vs2022的设置方式)
此时再打开反汇编就可以观察到,函数在此时直接展开
inline 特性
inline是一种以空间换时间的做法
如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。
内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求:
inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建 议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不 是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
假如有一个func()函数
func()函数有100行 1w个地方调用这个函数
假设inline展开了 100*1w
假设inline不展开 100+1w
《C++prime》第五版关于inline的建议:
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。
防止重复包含同一个头文件:
#pragma once = #ifdefy 1
#define
#endif
为什么要防止链接冲突?
在.h未做声明和定义分离,会导致在多个.c文件中包含头文件时出现报错,多重定义的报错。
现在我在我的程序中写道这样的代码:
Stack.cpp
#include"Stack.h"
Stack.h
#include<iostream>int Add(int a, int b) {return a + b; }
Test.cpp
#include"Stack.h"int main() {int ret = Add(1, 2);return 0; }
运行后就会出现这样的报错:
在.h未做声明和定义分离时,为什么会出现这样的报错?
在stack.cpp与test.cpp中都包含一份Add函数(不构成函数重载),在链接时,会导致文件中出现两个一样的函数,从而重复定义出现报错。
以下有三种解决方案:
1、当然就是声明和定义的分离
2、加静态static,修饰函数时,与全局函数相比,有链接属性,只在当前文件可见。(不会进符号表)
进符号表:将地址加到符号表是为了让别人方便调用
Stack.h
#include<iostream>static int Add(int a, int b) {return a + b; }
Stack.cpp
#include"Stack.h"
Test.cpp
#include<iostream>inline int Add(int a, int b) {return a + b; }
3、使用内联(内联也不支持声明与定义分离)
Stack.h
#include<iostream>inline int Add(int a, int b) {return a + b; }
直接在.h中定义解决方案,不在.cpp中定义解决方案,同static,Add地址不会进符号表,inline直接展开。
总结:若需在.h中定义函数:
小函数用:inline(并且,在函数很长时,实际上也不会展开内联函数)
大函数用:static or 声明与定义分离
七、auto关键字(C++11)
类型别名思考
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错
示例:
#include <string> #include <map> int main() {std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", "橙子" }, {"pear","梨"} };std::map<std::string, std::string>::iterator it = m.begin();while (it != m.end()){//....}return 0; }
std::map::iterator 是一个类型,但是该类型太长了,特别容 易写错。
这里可以通过typedef给类型取别名
比如:
#include <string> #include <map> typedef std::map<std::string, std::string> Map; int main() {Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };Map::iterator it = m.begin();while (it != m.end()){//....}return 0; }
使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题:
typedef char* pstring; int main() {const pstring p1; // 编译成功还是失败?const pstring* p2; // 编译成功还是失败?return 0; }
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的 类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义。
auto简介
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的 是一直没有人去使用它,大家可思考下为什么? C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一 个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
auto使用示例:
void func()
{
//....
}int main()
{
int i = 0;
int j = i;
auto k = i; //自动识别i的类型加给k
// auto x; //auto不能做参数,返回值可做,但是很坑
auto p1 = &i;
auto* p2 = &i; //指针指向i 地址
//auto* p3 = i;
auto& r = i;void(*pf1)(int, int) = func; //函数指针
auto pf2 = func;return 0;
}void func(int a, int b) {} int main() {void(*pf1)(int, int) = func;auto pf2 = func;cout << typeid(pf1).name() << endl;cout << typeid(pf2).name() << endl;return 0; }
运行这个代码:
auto自动识别类型
特别是当我们学到后期:
int main() {std::map<std::string, std::string> dict;//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();cout << typeid(it).name() << endl;return 0; }
虽然auto很方便很好用,但是大多数情况,我们最好还是自定义好类型
auto不能推导的场景
如下,若我们使用许多的auto,在我们的程序中会出现这样的问题:
1. auto不能作为函数的参数
返回值可做,但是很坑
void TestAuto(auto a) {} auto f2() {auto ret = 1;return ret; }auto f1() {auto x = f2();return x; }auto TestAuto() {auto a = f1();return a; }void func(int a, int b) {} int main() {auto it2 = TestAuto();return 0; }
2. auto不能直接用来声明数组
void TestAuto() {int a[] = {1,2,3};auto b[] = {4,5,6}; }
3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有 lambda表达式等进行配合使用。
八、基于范围的for循环(C++11)
范围for的语法
示例:
#include"Stack.h" #include <string> using namespace std;int main() {//在C++98中如果要遍历一个数组,可以按照以下方式进行:int array[] = { 1, 2, 3, 4, 5 };for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)array[i] *= 2;for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)cout << *p << endl;// C++11// 依次取数组中值赋值给e,自动迭代,自动判断结束for (auto& e : array){e *= 2;}cout << endl;for (auto e : array){cout << e << " ";}cout << endl;return 0; }
运行结果:
2
4
6
8
104 8 12 16 20
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因 此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范 围内用于迭代的变量,第二部分则表示被迭代的范围。
范围for的使用条件
1. for循环迭代的范围必须是确定的 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[]) {for(auto& e : array)cout<< e <<endl; }
2. 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在提一下,没办法 讲清楚,现在大家了解一下就可以了)
九、指针空值nullptr(C++11)
C++98中的指针空值 在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现 不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下 方式对其进行初始化:
void TestPtr() {int* p1 = NULL;int* p2 = 0;// …… }
在c语言中:NULL实际是一个宏且值为0
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何 种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦
比如:
void f(int) {cout<<"f(int)"<<endl; } void f(int*) {cout<<"f(int*)"<<endl; } int main() {f(0);f(NULL);f((int*)NULL);return 0; }
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的 初衷相悖。 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入 的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。 你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。