C++起源与发展:
C++由 Bjarne Stroustrup 在 20 世纪 80 年代初在 C 语言的基础上开发而来。其目的是为了增加面向对象编程的特性,同时保持 C 语言的效率和灵活性。早期的 C++主要引入了类、封装、继承和多态等面向对象的概念。
C++进程:
C++98,规范了语言的语法和语义,使得不同编译器对 C++的实现更加一致。
C++03,标准委员会对 C++98 进行了一些小的修订,发布了 C++03 标准。
C++11 标准发布,带来了众多重大的改进,如自动类型推导、范围 for 循环、lambda 表达式、右值引用等,极大地增强了语言的表现力和实用性。
C++14 标准发布,对 C++11 进行了一些小的改进和扩展。
C++17 标准发布,进一步改进了语言的特性,包括结构化绑定、if 初始化语句等。
C++ 20更新引入了一系列新特性和改进、概念、模块化等。
一、C++的第一个程序
首先回顾C语言的第一个程序:
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
C++兼容C语言绝大多数的语法,因此以上代码依然可以在C++程序中运行。
在vs上创建一个C++文件:将定义文件代码后缀改为.cpp。
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world\n"<<endl;
return 0;
}
二、命名空间(namespace)
2.1 namespace的价值
- 避免命名冲突:在大型项目中,若没有命名空间,不同的代码模块可能会定义相同名称的函数、变量或类、从而导致命名冲突。
- 组织代码结构:命名空间可帮助组织代码,使代码更具可读性和可维护性;可将相关的功能分组到一个命名空间中,以便更好地理解代码的结构。
- 控制访问权限:命名空间可作为一种控制访问权限的方式;可将一些内部实现细节放在特定的命名空间中,并限制对这些命名空间的访问,从而实现信息隐藏。
2.2 namespace的定义和使用
- 定义命名空间,namespace后⾯跟命名空间的名字,然后接⼀对{}即可,{}中即为命名空间的成员。
//1.pan是命名空间的名字
//2.{}里面的是命名空间的成员
namespace PanNamespace
{
//命名空间中可以定义变量/函数/类型
int rand=2;
int Add(int x,int y)
{
return a+b;
}
}
-
C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
-
可以使用命名空间限定符“::”来访问命名空间中的成员,例如:(第一个代码块是用C语言编译运行)
int main()
{
// 这⾥默认是访问的是全局的rand函数指针
printf("%p\n", rand);
// 这⾥指定PanNamespace命名空间中的rand
printf("%d\n", bit::rand);
return 0;
}
int main()
{
//std(stdandard)是一个命名空间
std::cout << PanNamespace::rand << std::endl;
PanNamespace::Add(a,b);
return 0;
}
- 也可以使用 using 指令或 using 声明来简化对命名空间成员的访问,这样可以在不使用命名空间限定符的情况下访问命名空间中的成员,但可能会增加命名冲突的风险。
using namespace PanNamespace;
int main()
{
std::cout << rand << std::endl;
Add(a,b);
return 0;
}
- namespace只能定义在全局,但它可以嵌套定义,例如:
namespace OutNamespace
{
namespace InnerNamespace
{
int nestedVariable = 20;
}
}
访问嵌套命名空间中的成员:
std::cout << OuterNamespace::InnerNamespace::nestedVariable << std::endl;
三、C++输入和输出
- 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。
- std::cin 是 istream 类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输⼊流。
- std::cout 是 ostream 类的对象,它主要⾯向窄字符的标准输出流。
- std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " \n" << b << "\n " << c << endl;
std::cout << a << " \n" << b << "\n " << c << std::endl;
// 可以⾃动识别变量的类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
四、缺省参数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。 (有些地⽅把缺省参数也叫默认参数)
-
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
-
带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
-
函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
#include <iostream>
using namespace std;
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1,2);
Func1(1,2,3);
Func2(10);
Func2(10,20);
Func1(10,20,30);
return 0;
}
输出为:
五、函数重载
在 C++中,函数重载(function overloading)是指在同一个作用域内,可以有多个函数具有相同的函数名,但参数列表不同。
函数重载实现方式:
- 参数列表不同,可以是参数类型不同、参数个数不同或者参数的顺序不同。
- 函数重载的判断是在编译时进行的,编译器会根据函数调用的参数类型和数量来确定调用哪个函数。
- 函数的返回值类型不能用于区分重载函数,不能有两个函数只有返回值类型不同而参数列表完全相同。
#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、参数个数不同
// 3、参数类型顺序不同
void Add(int a,char b,int c)
{
cout << a+b+c << endl;
}
void Add(int a, char b)
{
cout << a+b << endl;
}
void Add(char b, int a)
{
cout << b+a << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
Add(10,'a',20);
Add(10, 'a');
Add('a', 10);
return 0;
}
六、引用
6.1 引用的概念和定义
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。
类型& 引⽤别名 = 引⽤对象;
例如:
int a=10;
int& b=a;
int& c=a;//此处解释一个变量可有多个引用
6.2 引用的特性以及应用
-
一个变量可以有多个引用
-
引用一旦引用一个实体,再不能引用其他的实体(一旦一个引用被初始化指向一个对象,它就不能再指向其他的对象)
-
引⽤在定义时必须初始化
-
函数参数传递避免拷贝:在函数参数传递和返回值时,可避免对象的拷贝
-
函数参数传递允许修改传入对象:如果希望函数能够修改传入的对象,使用引用作为参数。
void f(int& a) { a++; } int main() { int num=5; f(num); cout<<num<<endl;//输出6 return 0; }
-
返回局部变量的引用是错误的:因为局部变量在函数结束后就被销毁,返回其引用会导致未定义行为。
int& Add(int a,int b)
{
int c=a+b;
return c;
}
int main()
{
int& ret=Add(2,3);
Add(3,4);
cout<<"Add(2,3):"<<endl;
return 0;
}
【注】由于C是别名的返回值,在调用完函数后,所调用的内存销毁返回系统空间,C不存在,以上代码块输出为空。
6.3 const 关键字引⽤
- 可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。
- 所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
int main()
{
int a = 10;
const int& ra = 30;
// 编译报错: “初始化”: ⽆法从“int”转换为“int &”
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:“初始化”: ⽆法从“double”转换为“int &”
// int& rd = d;
const int& rd = d;
return 0;
}
【注】int& rb=a * 3、int& rd=d这些情况都是将结果保存在临时对象中,而const限制int& a=30,int& rb=a * 3引用的是临时对象,C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。
6.4指针和引用的关系
相似之处:
都可以用于间接访问对象:指针通过存储对象的地址来间接访问对象。例如: int* ptr = &obj; ,这里 ptr 存储了对象 obj 的地址,可以通过 *ptr 来访问 obj 。引用也是对对象的一种间接访问方式,它是一个已存在对象的别名。例如: int& ref = obj; ,对 ref 的操作实际上就是对 obj 的操作。
-
语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
-
引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
-
引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
-
引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
-
sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
-
指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
也是对对象的一种间接访问方式,它是一个已存在对象的别名。例如: int& ref = obj; ,对 ref 的操作实际上就是对 obj 的操作。
-
语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
-
引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
-
引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
-
引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
-
sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
-
指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。