c++第一个程序
对于c++来说它兼容c的大部分语法,c++的出现,最开始是为了因为c的本身的缺陷与不便对其进行修改与提升。
我们使用的IDE仍然是vs2022,在这个环境下,c的文件的后缀是.c,c++的文件的后缀是.cpp,对于c的语法,在cpp为后缀的文件中仍然是可以运行,因为c++是在c的基础上发展而来的一种新的语言。
当我们学习c的时候我们的第一个程序,那肯定是hello world,在c++这里我们仍然是这样的
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
这是我们在学习c语言时的第一个程序,而在c++中,我们的第一个程序时这样的
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world"<<endl;
return 0;
}
二者输出的结果都是一样的,但书写的方式,基本不同。在这里涉及的东西,我们一一介绍
命名空间
在成员一映入眼帘的时一个由#include包裹的头文件,这个,我们能够理解,在第二行,出现了namespace这个关键字,它是什么意思呢?
在c++中,我们一定会涉及到大量的变量,函数,类。而这些东西都定义在全局域中,就可能会导致命名冲突,举个例子。
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
//编译报错:
//error C2365: “rand”:
//重定义;以前的定义是“函数”
printf("%d\n", rand);
return 0;
}
在这个程序运行的时候,会报错。
这里定义了一个全局变量rand。还有两个头文件,在c语言学习的时候,在生成可执行程序的时候会经过编译链接的阶段,在这里的预处理阶段,会将这里头文件进行展开,头文件的展开的本质是将头文件里面的东西拷贝到当前文件,stdlib这个头文件中有一个函数的名字是rand,在这里就与全局变量rand冲突了。所以为了避免这个问题,c++提出了命名空间这个概念。
(1) 命名空间的定义
1.定义命名空间,需要用到关键字namespace这个关键字,在这个关键字后面跟着这个命名空间的名字,用空格隔开,接着跟着一堆{},在这个花括号里面可以定义命名的空间的成员,函数,变量,声明等。
2.namespace本质上是定义一个域,在c阶段我们知道了全局域,局部域。对与这两个域来说,他们都各自有自己的生命周期和作用规则,在c++阶段,我们会再学习两个域,命名空间域和类域
这两个域只影响作用规则,因为二者只能定义在全局。所以他们并不影响生命周期,他们的生命周期仍然是全局的。
3.对于域来说,它查找的时候会优先查找局部域接着去查找全局域,如果我们不去指定域的话,他们并不会去查找类和命名空间。
4.命名空间可以嵌套定义。
5.在不同文件的同名命名文件 不会冲突,他们会合并。
6.c++标准库都放在一个叫std的命名空间里
(2)命名空间的使用
对于命名空间的指定使用有三种
1.指定访问
std::cout<<"mutou"<<endl;
2.将某个成员展开
using std::cout;
int main()
{
cout<<"123"<<endl;
}
3.全展开
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world"<<endl;
return 0;
对于这三种方式,我们在公司或者项目中推荐使用第一个。在一些频繁使用的方法,我们推荐使用第二个。在日常我们自己的程序中,不与其他命名冲突的情况下,我们建议第三种。
c++的输入输出
ioshream是InputOutputStream的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输
出对象。
cin和cout是istream和ostream两个类中的对象,他们是c++中用来输入输出的方法,相对于printf和scanf,他们最大的特点是可以自动识别类型。
endl是一个函数,它可以控制换行,但与\n不同的是他会同时刷新缓冲区
因为cin,cout,endl都在c++的标准库中,我们使用的时候要以命名空间使用
在vs中,stdio.h在iostream这个头文件间接包含了,而在其他的编译器可能没有。
缺省参数
1.缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把缺省参数也叫默认参数)。
2.缺省值需要从右到左给,否则在传参的时候可能会产生歧义。
3.对于缺省参数的函数来说,它的实参需要从左到右给,不能跳跃给,否则也可能造成歧义。
#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(100);
Func2(100, 200);
Func2(100, 200, 300);
return 0;
}
4.函数定义和声明分离时,缺省参数不能同时出现在声明和定义中,规定缺省参数定义到函数的声明中
//test.h
int add(int a, int b=3);
//test.cpp
int add(int a, int b)
{
return a + b;
}
函数重载
在c语言阶段,同一个域中不能定义同名函数,而在c++中,支持同一个域中定义同名函数,但要求他们的参数不同。
1.参数类型不同
// 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.参数个数不同
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
3.参数类型顺序不同(本质时参数类型不同)
// 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;
}
4.返回值不同不能最为函数重载的条件,因为编译器不能根据,函数的返回结果去分辨函数的不同,才函数调用的时候,函数不知道会调用哪个。
引用
对于引用来说,它时给一个变量取别名,并不是重新去创建变量。它与原先的变量共用同一个内存空间,编译器并不会为了一个引用变量去创建一个新的内存空间。
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
(1)引用的特性
1.对于引用来说,它必须去初始化。
2.同一个变量,可以有多个引用
3.引用只能引用一个对象,不能去改变,引用多个对象
#include<iostream>
using namespace std;
int main()
{
int a = 10;
// 编译报错:“ra”: 必须初始化引⽤
//int& ra;
int& b = a;
int c = 20;
b = c;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
(2)引用的使用
在c++中引用和指针时相辅相成的,两者有相同的地方但不完全一样
他们的关系就像上图一样。
对于引用来说,它比较便利的时做引用传参,和返回值的时候,提高拷贝的效率。改变引用对象的同时去改变被应用对象。
1.引用传参
在用c实现数据结构的时候,有的时候我们因为要修改头结点,而去传二级指针,为什么要穿二级指针,因为每一个结点都由一个指针指向,当我们去修改头结点的时候,我们就要对头节点的指针进行修改,我们就需要去修改这个指针的地址,在这里我们就需要传二级指针。而这个地方如果我们去用引用去做参数的话,我们只需要传一个一级指针,即可,因为根据引用这个定义,我们这个引用参数时,是对这个一级指针去取别名,它与这个一级指针是共用同一块内存空间的。这个时候我们就相当于直接对这个指针进行修改。
void init(struct st*& arr);
我们就可以进行这样的修改。
同样的还有一些类似于两个值相互交换的函数,我们也可以这样改。
void swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
2.引用作返回值
对于引用做返回值,它的好处是返回值可以直接进行修改。
#include<iostream>
using namespace std;
int& change(int* arr)
{
return arr[0];
}
int main()
{
int arr[10] = { 1,2,34,4,5 };
cout<<change(arr)<<endl;
change(arr)+=2;
cout<<change(arr)<<endl;
return 0;
}
如果这个函数,它的返回值如果不是个引用,而是一个整型,那么它会报错。
因为对于函数的返回值是一个整型来说它是将一个临时对象进行拷贝返回,而这个临时对象是具有常性的,它是只可读的右值。
如果引用来说,它是对这个函数的返回结果起了一个别名,它的返回值是一个左值,可以被修改。
但是这个引用的做返回值,是由条件的,它返回的值,必须在这个函数栈帧销毁之后仍然存在。
如果他是类似于下列这种
int& add(int a,int b)
{
return a+b;
}
它仍然是报错的,因为这个里面的三个对象a,b,a+b,在这个函数栈帧的销毁的同时也销毁了,它变量的内存空间销毁了之后,它的引用就无从指向了,它就是成为一个野引用(在汇编来看,引用和指针的底层指令是大差不差的);
(3) const引用
可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访
问权限在引⽤过程中可以缩⼩,但是不能放⼤。
不需要注意的是类似int& rb = a3; double d = 12.34; int& rd = d; 这样⼀些场景下a3的和结果保存在⼀个临时对象中,int& rd = d 也是类似,在类型转换中会产⽣临时对象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,
C++中把这个未命名对象叫做临时对象。
int main()
{
const int a = 10;
// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”
// 这⾥的引⽤是对a访问权限的放⼤
//int& ra = a;
// 这样才可以
const int& ra = a;
// 编译报错:error C3892: “ra”: 不能给常量赋值
//ra++;
// 这⾥的引⽤是对b访问权限的缩⼩
//rb++;
int b = 20;
const int& rb = b;
// 编译报错:error C3892: “rb”: 不能给常量赋值
return 0;
}
#include<iostream>
using namespace std;
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;
}
inline
对于正常函数来说,调用它会产生函数栈帧,对内存产生消耗,而用inline修饰的函数就像宏一样,它会在需要,它的地方直接展开,但是,是否采用inline是要由编译器负责,如果代码过长,或者这个函数是递归的函数的话,编译器并不会去采用。在c++中我们推荐使用inline和enum去代替宏。
inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地
址,链接时会出现报错。
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
int ret = x + y;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
int main()
{
// 可以通过汇编观察程序是否展开
// 有call Add语句就是没有展开,没有就是展开了
int ret = Add(1, 2);
cout << Add(1, 2) * 5 << endl;
return 0;
}
nullptr
在c++中我们使用nullptr来表示空指针,C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。f((void*)NULL);调⽤会报错。C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。