幸福比傲慢更容易蒙住人的眼睛。 ——大仲马
C++入门
- 1、属于C++的关键字
- 1、1、C++从何而来
- 1、2、C++关键字(C++98)
- 2、命名空间
- 2、1、命名空间的定义
- 2、2、命名空间使用
- 3、C++输入和输出
- 4、缺省参数
- 4、1、缺省参数概念
- 4、2、缺省参数分类
- 5、函数重载
- 5、1、函数重载概念
- 6、引用
- 6、1、引用概念
- 6、2、引用特性
- 6、3、常引用
- 6、4、使用场景
- 6、5、传值、传引用效率比较
- 6、6、引用和指针的区别
- 7、内联函数
- 7、1、概念
- 7、2、特性
- 7、3、inline细节
- 8、auto关键字(C++11)
- 8、1、auto介绍
- 8、2、auto使用规则
1、属于C++的关键字
1、1、C++从何而来
为什么会出现C++呢?为什么C++的关键字和C语言相对比起来,有着不少的增加?
其实想要搞明白为什么会出现这种状况,首先要明白的是C++是祖师爷用C语言的过程中,饱受C语言中一些限制的诟病,在C语言的基础上增加的一个更牛的系统。
Bjarne Stroustrup就是他创建的C++。
所以,对于学习过C语言的人来说,C++是一款不可多得的好用的语言。在基础之上为我们解决一些C语言不能够很好处理的问题,给我们节约时间,拥有更多的库,不再像C语言那样,还需要我们去造轮子。
1、2、C++关键字(C++98)
在这个版本中,总计是63个关键字,而C语言的关键字是32个,有了很大的提升。相对于C来说,提升了很多,也更方便了。
2、命名空间
为什么要有命名空间的这一术语?因为在C/C++中,变量,函数以及类都是大量存在的,这些都将存储于全局作用域,那么就可能会引起冲突。使用命名空间,就可以将标识符的名称本地化,从而避免命名冲突,而运用命名空间来解决问题的关键字就是namespace。
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{printf("%d\n", rand);
return 0;
}
// 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
这就是很好的证明,C++之中namespace的便捷。
2、1、命名空间的定义
定义命名空间,需要使用namespace关键字,后面跟上的是命名空间的名字,然后街上一对{},在{}之中即为命名空间的成员。
// hehe是命名空间的名字,一般开发中是用项目名字做命名空间名。
// 我们上课用的是bit,大家下去以后自己练习用自己名字缩写即可,如张三:zs
// 1. 正常的命名空间定义
namespace hehe
{// 命名空间中可以定义变量/函数/类型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}
//2. 命名空间可以嵌套
// test.cpp
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;}}
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
// ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
// test.h
namespace N1
{
int Mul(int left, int right){return left * right;}
}
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
2、2、命名空间使用
那么有了命名空间,该如何去使用不同作用域的成员呢?
namespace bit
{// 命名空间中可以定义变量/函数/类型int a = 0;int b = 1;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}
int main()
{// 编译报错:error C2065: “a”: 未声明的标识符printf("%d\n", a);
return 0;
}
那我们该怎么去找到这里的a呢?
命名空间的三种方法:
1、在使用前在上空间名称以及作用域限定符
int main()
{printf("%d\n", N::a);return 0;
}
2、使用using将命名空间中的某个成员引入
using N::b;
int main()
{printf("%d\n", N::a);printf("%d\n", b);return 0;
}
3、使用using namespace+命名空间 引用
using namespce N;
int main()
{printf("%d\n", N::a);printf("%d\n", b);Add(10, 20);return 0;
}
通常而言,第三种在大型的组合团队中不要使用,因为标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。但是对于正常的人来说,写一些正常的程序,还是建议使用第三种的,不仅方便,还能理解到命名空间关键字的意义。
3、C++输入和输出
对于C来说的“hello world ”是我们学习的第一句话,那么关于C++来说,我们该怎么去让他在屏幕上显示出来呢?
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
说明:
1、 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<
iostream >头文件中。
3. <<是流插入运算符,>>是流提取运算符。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C++的输入输出可以自动识别变量类型。
5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。
注意: 早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用+std的方式。
4、缺省参数
4、1、缺省参数概念
缺少参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数的时候,如果没有指定的实参则采用该形参的缺省值,否则使用指定的参数。
void Func(int a = 0)
{cout<<a<<endl;
}
int main()
{Func(); // 没有传参时,使用参数的默认值Func(10); // 传参时,使用指定的实参
return 0;
}
4、2、缺省参数分类
全缺省参数
void Func(int a = 10, int b = 20, int c = 30){cout<<"a = "<<a<<endl;cout<<"b = "<<b<<endl;cout<<"c = "<<c<<endl;}
半缺省参数
void Func(int a, int b = 10, int c = 20){cout<<"a = "<<a<<endl;cout<<"b = "<<b<<endl;cout<<"c = "<<c<<endl;}
注意:
1、半缺省参数不是指只有一半,而是必须从左往右依次来给出,不能间隔。
2、缺省参数不能再函数声明和定义中同时出现。
//a.hvoid Func(int a = 10);// a.cppvoid Func(int a = 20){}// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该
用那个缺省值。
3、缺省值必须是常量或全局变量
4、C语言不支持(编译器不支持)
5、函数重载
在现实世界,一个词常常有着不同含义,我们可以同上下问之间的关系来判断词的真正含义,即使该词重载了。
重载从词语上的意思来说,从我的感觉来说,也可以看作是,重复的载入。
以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!
5、1、函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些类型的形参列表(参数个数 或 类型 或 类型顺序不同)。
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}
// 2、参数个数不同
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}
int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}
6、引用
6、1、引用概念
引用不是创建一个新的变量,而是给已经存在的变量取别名,变量和变量的别名共同使用一块内存空间。
引用相当于给别人起外号,如果本人做了什么,或者说是别名做了什么,都是改变同一个人。
比如:李逵你吃过了吗,李逵吃过了,那黑旋风一定也是吃过了。
类型& 引用变量名(对象名)=引用实体
void TestRef()
{int a = 10;int& ra = a;//<====定义引用类型printf("%p\n", &a);printf("%p\n", &ra);
}
注意:引用类型必须是和实体是同种类型的
6、2、引用特性
1、引用并不能在定义的时候不初始化。
2、一个变量可以有多个引用
3、引用一旦引用一个实体,再也不能引用其他实体。
void TestRef()
{int a = 10;// int& ra; // 该条语句编译时会出错int& ra = a;int& rra = a;printf("%p %p %p\n", &a, &ra, &rra);
}
6、3、常引用
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;
}
6、4、使用场景
1、做参数
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;
}
2、做返回值
int& Count()
{static int n = 0;n++;// ...return n;
}
下面代码中,输出的结果是什么呢?
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;
}
6、5、传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{// 以值作为函数的返回值类型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock();// 以引用作为函数的返回值类型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock();// 计算两个函数运算完成之后的时间cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
通过上述的代码比较,不难发现传值和指针在作为传参以及返回值类型上面的效率相差很大。
6、6、引用和指针的区别
在语法层面上引用就是别名,没有独立的空间,和引用的实体共同用一块空间。
但是,底层实现上实际是有空间的,因为引用是按照指针的方式去实现的。因为C++就是在C的基础上来进行进化的。
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
我们通过什么知道这两个是一样的呢?当然是通过计算机编译器的底层汇编来看啊。
引用和指针的不同点:
1、引用概念上的定义是变量的别名,指针存储一个变量的地址。
2、引用在定义的时候就必须初始化,指针没有要求。
3、引用在初始化时引用一个实体之后,就不能引用其他实体,而指针可以在任何时候指向任何一个同类型的实体。
4、没有NULL引用,但是有NULL指针。
5、sizeof的计算不同,引用的结果为引用类型的大小,但是指针始终是地址空间所占字节个数(32位平台下占4个字节)
6、引用自加1,即实体加1。指针自加则是向后偏移一个类型的大小。
7、有多级指针,但是没有多级引用
8、访问方式不同,指针需要显示解引用,引用编译器自己处理。
9、引用比指针使用起来相对来说更安全。
7、内联函数
7、1、概念
在函数最开始的时候用inline修饰的函数叫做内联函数,C++编译器会在调用内联函数的地方展开。但是由于编译器的不断提升,现在的编译器已经会判断函数是否应该展开,如果太长也不会展开的。那为什么需要**inline呢?**那是因为不需要调用函数,也就意味着没有函数调用建立栈的开销,内联函数提升程序运行的效率。
这是正常的函数调用。
而这时内联函数的展开。
7、2、特性
1、 inline函数是一种空间换时间的做法,如果编译器将函数当作内联函数处理,在编译阶段,会用函数体替换函数调用
2、 inline对于编译器来说只是一个建议,不同的编译器实现的机制可能不同,一般来说:函数规模较小(函数不是很长,具体还是取决于编译器)、不是递归、且频繁的调用的函数采用inline,否则编译器将会忽略inline的特性。
3、 inline不建议声明和定义分离 ,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
7、3、inline细节
首先看一下宏和函数的区别。
宏的优点和缺点在上述的图片中已经很清楚了,那么有没有什么办法能够在这些的优点上进行对缺点的改变?
C++中的可以代替宏的方案
1、常量定义 换成const enum
2、短小函数定义 换成内联函数
8、auto关键字(C++11)
随着程序的复杂化,类型的使用也将会是多样化。这就导致
1、类型难于拼写
2、含义不明导致出错
8、1、auto介绍
auto不再是一个存储类型指示符,而是作为一
个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
==注意:==使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto
的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编
译期会将auto替换为变量实际的类型
8、2、auto使用规则
1、 auto与指针和引用结合使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须
加&。
2、 同一行定义多个变量时,所有的变量必须是相同类型。
3、 auto不能作为函数的参数。同时,也不能声明数组。