0.前言
1979年,贝尔实验室的本贾尼在C语言的基础上进行拓展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes。
C++并不是一次性就完成的,而是经历了多次更新,由浅入深,不断完善而形成的。目前公司主流使用的还是C++98和C++11。
C++出现至今,仍是主流的计算机语言之一,所以学好C++,工作中的应用场景十分丰富。
1.C++关键字
C++关键字有63个,C语言中的关键字有32个(红色字体)。
2.命名空间
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。
#include<stdio.h>
int rand = 10;
//C语言中无法解决命名冲突的问题,所以C++提出了namespace解决
int main{
printf("%d\n",rand);\
return 0;
}
定义命名空间,关键字为namespace ,后面跟命名空间的名字,然后将成员放在一对{ }中。
bit 是命名空间的名字
命名空间中可以定义变量 、函数、 类型
namespace bit
{
int rand = 0;
int Add(int a, int b){
return a + b;
}
struct Node{
struct Node* next;
int val;
};
}
命名空间可以嵌套
namespace N1{
int a;
int b;
int Add(int a, int b){
return a + b;
}
namespace N2{
int c;
int d;
int Sub(int x, int y){
return x - y;
}
}
}
同一个工程中允许有多个同名的命名空间,编译器会将他们合成到同一个命名空间中。
例如:下面代码中与上个代码中N1同名,在同一个项目中时,会被合并成一个命名空间。
namespace N1{
int Mul(int left, int right){
return left * right;
}
}
命名空间的使用
加命名空间名称及作用域限定符
使用using+命名空间中的某个成员 引入
使用using namespace +命名空间名称 引入
namespace bit {
int a = 0;
int b = 9;
int c = 10;
}
//加命名空间名称及作用域限定符
int main() {
printf("%d ",bit::rand);
return 0;
}
//使用using将命名空间中某个成员引入
using bit::c;
int main() {
printf("%d ", c);
printf("%d ", bit::a);
printf("%d ", bit::b);
return 0;
}
//使用using namespace命名空间名称引入
using namespace bit;
int main()
{
printf("%d ", a);
printf("%d ", b);
printf("%d ", c);
return 0;
}
3.C++输入和输出
<iostream>是必须要包含的头文件,std是C++标准库中的命名空间名,C++将标准库的定义实现都放在这个命名空间中。
cout和cin是全局的流对象,endl是C++的特殊符,表示换行输出。
<< 是流插入运算符,>>是流提取运算符。
cin和cout不用区分数据类型,可以直接输入输出,比printf和scanf方便。
#include<iostream>
using namespace std;+
int main()
{
int a;
double b;
float c;
//cout和cin是全局的流对象
//endl是换行,不想换行就不加endl
cout << "Hello world!" << " "<<"你好世界! " << endl;
//cin不用区分数据类型,可以直接输入
cin >> a >> c >> b;
//如果需要小数保留还继续使用printf会更方便
printf("%d %.2lf %.3lf", a, b, c);
return 0;
}
如果需要小数保留还继续使用printf会更方便,因为cin和cout使用会更加复杂。
4.缺省参数
缺省参数是声明或定义函数是为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
#include<iostream>
using namespace std;
void Func(int a = 0) {
cout << a << endl;
}
int main()
{
Func();//没有传参,使用参数的默认值
Func(1);//传参时,使用指定的实参
return 0;
}
缺省参数的分类
全缺省参数
#include<iostream>
using namespace std;
void Func(int a = 0, int b = 9, int c = 8) {
cout <<"a = " << a << endl;
cout <<"b = " << b << endl;
cout <<"c = " << c << endl;
}
半缺省参数
void _Func(int a , int b = 9, int c = 8) {
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main() {
Func(1);
printf("\n");
Func(1,2);
printf("\n");
Func(1,2,3);
printf("\n");
//半缺省参数必须从右向左依次来给出,不能间隔给
_Func(1);
printf("\n");
_Func(1, 2);
printf("\n");
_Func(1, 2, 3);
return 0;
}
使用时需注意:
半缺省参数必须从右向左依次来给出,不能间接着给。
缺省参数不能在函数声明和定义中同时出现。
//a.h
void Func(int a = 10);
//a.cpp
void Func(int a = 10);
{}
//如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那么编译器是无法确定到底改用哪个缺省值的
缺省参数必须是常量或者全局变量。C语言不支持缺省参数。
5.函数重载
函数重载,是C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
参数类型不同
#include<iostream>
using namespace std;
//函数重载
//1,参数类型不同
void Swap(int a, int b) {
//……
}
void Swap(char a, int b) {
//……
参数个数不同
//2,参数个数不同
void Swap(int b) {
//……
}
void Swap() {
//……
}
参数顺序不同
//3,参数类型顺序不同
void Swap(int a, char b) {
//……
}
void Swap(char a, int b) {
//……
}
6.引用
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
引用特性
void TestRef(){
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
常引用
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;
}
使用场景
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
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;
}
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
void TestRefAndValue()
{
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;
}
#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;
}
引用和指针的区别
int main()
{
int a = 10;
int& ra = a;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
return 0;
}
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
7.内联函数
概念
特性
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl
f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用
8.auto关键字(C++11)
#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;
}
#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 char* pstring;
int main()
{
const pstring p1; // 编译成功还是失败?
const pstring* p2; // 编译成功还是失败?
return 0;
}
auto的使用细则
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
auto不能推导的场景
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
9.基于范围的for循环(C++11)
void TestFor()
{
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;
}
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
10.指针空值—nullpur(C++11)
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
f(nullptr);
return 0;
}