前言
我们已经学习了C语言,而C++与C语言有着千丝万缕的联系,那么我们废话不多说,正式进入C++的学习
什么是C++
我们之前学习过C语言,而C语言是结构化和模块化的语言,比较适合处理较小规模的程序。而对于相对复杂的问题和较大规模的程序,需要高度抽象的建模的时候,C语言在使用的时候就没有那么合适,此时C++就应运而生
C++是兼容C语言的,在C++里面使用C语言的语法是完全可行的
在VS中,编译器是通过后缀来调用的【.c调用的是C语言的编译器,.cpp调用的是C++的编译器】
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main(void)
{
printf("hello world");
return 0;
}
而如果要用C++的语法写 hello world 则要写成以下形式:
cpp
#include<iostream>
using namespace std;
int main(void)
{
cout << "hello world" << endl;
return 0;
}
1.C++的命名空间
我们在使用C语言的时候通常会发现一个问题:我们在使用C语言的时候经常会出现命名冲突的问题
那么什么是命名冲突呢?我们来举一个实际的例子:
当我们定义一个变量rand并且给它赋值、打印的时候,我们发现rand可以被打印出来
cpp
#include <stdio.h>
int rand = 0;
int main(void)
{
printf("%d\n", rand);
return 0;
}
而当我们包含一个头文件------<stdlib.h>的时候就会报错
cpp
#include <stdio.h>
#include <stdlib.h>
int rand = 0;
int main(void)
{
printf("%d\n", rand);
return 0;
}
这是为什么呢?因为头文件<stdlib.h>的里面创建了名字叫做rand的变量,如果使用者直接创建一个名字叫做rand的变量时,就会与库文件里面创建的rand变量产生命名冲突的问题,编译器就不知道该用这两个变量中的哪一个。因为我们无法修改头文件里面的变量,我们此时就只能够修改自己的变量的名字
因为C语言在使用的过程中需要连接多个库,所以非常容易出现命名冲突的问题,那么我们在使用的过程中就会非常难受,需要不断地修改变量的名字
C++就创建了一个叫做命名空间的东西,其使用起来和C使用结构体很相似
cpp
namespace xiaobai
{
int rand = 0;
}
命名空间的名字是可以自定义的,用户可以随意地取名字
当我们没有指定使用命名空间里面的变量的时候,编译器就会默认的从全局变量里面查找并且使用
我们可以以下方式来访问命名空间里面的变量:
使用域作用限定符::
cpp
namespace xiaobai
{
int rand = 0;
}
int main(void)
{
//域作用限定符
printf("%d\n", xiaobai::rand);
return 0;
}
域作用限定符限定的是编译器在编译过程中查找的规则,在没有限定的情况下默认是在全局中进行查找
命名空间中可以定义变量、函数、类型
cpp
namespace xiaobai
{
int rand = 0;
int Add(int x, int y)
{
return x + y;
}
}
int main(void)
{
//域作用限定符
printf("%d\n", xiaobai::rand);
printf("%d\n", xiaobai::Add(2, 7));
return 0;
}
我们发现,如果我们需要大量的使用命名空间内的某个变量或者函数的时候,每次在使用时都需要展开命名空间就会比较繁琐,那么我们有什么办法可以一次性的展开呢?答案是有的
我们此时就可以使用 using namespace (命名空间的名字)来展开命名空间
cpp
namespace xiaobai
{
int rand = 0;
int Add(int x, int y)
{
return x + y;
}
}
using namespace xiaobai;
int main(void)
{
//域作用限定符
printf("%d\n", rand);
printf("%d\n", Add(2, 7));
return 0;
}
但其实展开命名空间是个比较危险的行为,为什么这么说呢?我们来看下图:
我们发现展开了命名空间以后代码就发生了错误,因为此时命名空间里面的rand变量也被展开,而在头文件<stdlib.h>中恰好也有一个rand变量,此时编译器又不知道该调用哪一个变量了
要解决这个问题的话,我们就不能一次性展开命名空间里面的所有内容,而是要展开某个指定的变量或者函数,我们可以用 using (命名空间名字) (命名空间中的变量)来展开命名空间中指定的元素
cpp
namespace xiaobai
{
int rand = 0;
int Add(int x, int y)
{
return x + y;
}
}
using xiaobai::Add;
int main(void)
{
//域作用限定符
printf("%d\n", rand);
printf("%d\n", Add(2, 7));
return 0;
}
这种展开方式叫做部分展开(部分授权)
命名空间可以使用"套娃操作",命名空间里面还可以包含命名空间,但是展开的时候需要展开两次
cpp
namespace xiaobai
{
int rand = 0;
int Add(int x, int y)
{
return x + y;
}
namespace xiaobai2
{
int rand = 1;
}
}
using xiaobai::Add;
int main(void)
{
printf("%d\n", xiaobai::rand);
printf("%d\n", xiaobai::xiaobai2::rand);
return 0;
}
学到这里,我们就可以解释我们之前C++代码中编写的:
cpp
using namespace std
的含义了,在头文件<iostream>中,<iostream>把所有的东西都封装到了namespace std中,我们在默认的情况下无法调用到命名空间std中的内容,所以我们需要使用到以上代码来展开命名空间std里面的所有内容
这里的展开与头文件的展开有很大的区别,头文件的展开指的是把头文件里面的内容拷贝到我们的代码之中,而命名空间的展开可以理解为授权,没有对命名空间的授权就无法使用命名空间里面的内容
基于这些理论,我们还有一种访问方式:
cpp
#include<iostream>
int main(void)
{
std::cout << "hello world" << std::endl;
return 0;
}
在这种情况下,我们没有全部展开命名空间std
或者我们可以部分展开命名空间:
cpp
#include<iostream>
using std::cout;
using std::endl;
int main(void)
{
cout << "hello world" << endl;
return 0;
}
我们需要注意:当我们定义多个命名空间并且命名空间的名字相同的时候,这种情况下并不会报错,编译器会把同名的命名空间合并为一个命名空间,命名空间不存在冲突的问题
在大型的项目中使用命名空间的时候最好不要全部展开
2.C++的输入和输出
我们先来看一下我们开头写过的程序:
cpp
using namespace std;
int main(void)
{
cout << "hello world" << endl;
return 0;
}
cpp
using namespace std;
int main(void)
{
cout << "hello world" << endl;
int i = 0;
cin >> i;
cout << i << endl;
return 0;
}
1.我们先来看一下cout:cout是一个对象,其中cout中的c指的是控制台(console),cout的意思就是往控制台里面输出某些内容,cout被称作流插入,而"<<"符号被称作流插入运算符
2.endl(end of line)表示的是一个换行符,也可以写作 '\n'
3.cin被称作流提取,可以往变量里面输入内容,而">>"符号被称作流提取操作符
4.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含头文件< iostream >
5.C++的输入和输出更加方便,因为C++输入和输出内容的时候会自动识别数据的类型,而C语言中需要手动输入格式,自动识别类型的本质是函数重载(暂时不需要了解,后面会学习)
cpp
int main(void)
{
int i = 1;
double j = 1;
std::cin >> i >> j;
cout << i << endl;
cout << j << endl;
cout << &i << endl;
cout << &j << endl;
return 0;
}
这时候可能有人会存在疑问:怎么控制浮点数小数点的位数呢?在C++中其实也是可以控制位数的,因为C++是兼容C语言的写法的,所以我们在大多数情况下可以用C语言的写法来控制小数点后的位数的,如果要使用C++自己的写法来控制小数点后的位数就会比较麻烦,需要调用函数
3.C++的缺省参数
3.1缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实 参则采用该形参的缺省值,否则使用指定的实参
cpp
void Func(int a = 1)
{
cout << a << endl;
}
int main(void)
{
Func(2);
Func();
return 0;
}
3.2缺省参数的分类
1.全缺省参数
cpp
void Func(int a = 1, int b = 2, int c = 3)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << endl;
}
int main(void)
{
Func();
Func(10);
Func(10, 20);
Func(10, 20, 30);
return 0;
}
我们需要知道,若是只往函数中传入一个参数的时候,是默认传给创建的第一个变量的,不能指定的传给第二个或者后面的变量。显示传参,传入参数时是从左往右依次传参的
2.半缺省传参
cpp
void Func(int a, int b = 2, int c = 3)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << endl;
}
int main(void)
{
Func(10);
Func(10, 20);
Func(10, 20, 30);
return 0;
}
半缺省传参的意思并不是传入一半的参数,而是传入部分参数
半缺省传参必须至少传入一个参数,而且必须是从右至左给缺省值
半缺省参数的应用
在C语言中,如果我们需要开辟一个100个字节的栈空间,我们一开始会开辟一个自定义大小的小空间(一般为4个字节)如果开辟的空间小于需求的空间,就会采取扩容的操作,以此类推,直到开辟的空间大小大于或者等于需要开辟的空间的大小,但是扩容有很高的时间和空间代价
cpp
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
#define N 4
void StackInit(ST* ps)
{
ps->a = malloc(sizeof(int)*N);
ps->top = 0;
ps->capacity = 0;
}
如果我们一开始就能够知道具体需要扩容的空间是多大就可以避免扩容,可以直接开辟需求空间的大小,如果我们不知道具体需要空间的大小的话就还是从采取扩容操作,这样可以在一定的程度之上节约时间和空间成本,而完成这个操作就需要使用半缺省参数
cpp
void StackInit(ST* ps,int N = 4)
{
ps->a = malloc(sizeof(int)*N);
ps->top = 0;
ps->capacity = 0;
}
在我们知道要开辟多大空间的情况下,我们就往函数中传入需要开辟的空间的大小;在我们不知道要开辟多大空间的情况下,我们就不传入参数,还是使用默认的4个字节,并且要它自己去扩容
我们需要知道的是,声明和定义之中不能同时给出缺省参数,缺省参数应该在声明中给,定义之中不给缺省参数,不然会报错
4.C++中的函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似而数据类型不同的问题
在不同的命名空间里面的同名函数是不构成函数重载的,因为它们不在同一个作用域,如下所示:
cpp
namespace xiaobai1
{
void Func(int x)
{}
}
namespace xiaobai2
{
void Func(double x)
{}
}
重载是"一词多义"的意思
函数重载在C语言中是不被允许的,因为C语言不允许同名函数。在C++中是可以的,但是要求构成函数重载,函数重载的要求如下:
1.函数名相同
2.参数不同:参数不同的形式有三种:参数的类型不同、参数的个数不同、参数的顺序不同
1>:参数的类型不同
cpp
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;
}
int main(void)
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
return 0;
}
我们可以发现,函数重载中也存在着类型自动识别
2>:参数的个数不同
cpp
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main(void)
{
f();
f(1);
return 0;
}
3>:参数类型的顺序不同
cpp
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()
{
f(10, 'a');
f('a', 10);
return 0;
}
返回值不同是不能构成重载的
结尾
本节我们敲响了C++学习的大门,C++相较于C语言有很大的优势,但是学起来并不是很轻松,希望能给你带来一定的帮助,谢谢您的浏览!!!