C++初阶篇----新手进村
目录
- 一、什么是C++
- 二、C++关键字
- 三、命名空间
- 3.1命名空间的定义
- 3.2命名空间的使用
- 四、C++输入和输出
- 五、缺省参数
- 5.1缺省参数的概念
- 5.2缺省参数的分类
- 六、函数重载
- 6.1函数重载的概念
- 6.2函数重载的原理----名字修饰
- 七、引用
- 7.1引用概念
- 7.2引用特性
- 7.3常引用
- 7.4引用的使用
- 7.5传值、传引用效率比较
- 7.6引用和指针的区别
- 7.7引用与指针的不同点
- 八、内联函数
- 8.1内联函数概念
- 8.2 特性
- 九、auto关键字
- 9.1类型别名思考
- 9.2 auto简介
- 9.3 auto的使用细则
- 9.4 auto不能推导的场景
- 十、C++范围的for循环(C++11)
- 10.1范围for的语法
- 10.2范围for的使用条件
- 十一、nullptr指针空值(C++11)
一、什么是C++
二、C++关键字
相较于C语言,C语言有32个关键字,而C++则有63个关键字
三、命名空间
在C/C++中,变量、函数等的类都是大量存在的,这些变量、函数和类的名称将都存
在于全局作用域中,可能会导致很多冲突。
使用命名空间的目的是对标识符的名称进行本地化,
以避免命名冲突或名字污染,**namespace
**关键字的出现就是针对这种问题的
3.1命名空间的定义
命名空间的普通定义
//命名空间的普通定义
namespace bit
{
int rand = 10;
int scanf = 2;
int Add(int x, int y)
{
return x + y;
}
}
命名空间的嵌套定义
//命名空间的嵌套定义、
namespace bit1
{
int rand = 10;
int scanf = 2;
int Add(int x, int y)
{
return x + y;
}
namespace bit2
{
int c;
int d;
int Sub(int left, int right)
{
return left + right;
}
}
}
同一个命名空间的合并
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
3.2命名空间的使用
作用域:
符号**“::”**在C++中叫做作用域限定符,我们通过“命名空间名称::命名空间成员”便可以访问到命名空间中相应的成员
加命名空间名称及作用域限定符,嵌套调用可以用"xxx::bbb::"
std::cin >> i;
cout << "xxxx" << endl;
cout << bit::rand << endl;
使用using将命名空间中某个成员引入
using std::cout;
using std::endl;
使用using namespace 命名空间名称 引入
using namespace bit;
int main()
{
int i = 0;
std::cin >> i;
cout << "xxxx" << endl;
cout << bit::rand << endl;
Add();
}
四、C++输入和输出
std是C++标准库的命名空间名,C++将标准库的定义实现都放在这个命名空间中
使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。
输出流:cout和endl,其中endl表示换行输出.他们都包含在的头文件中 输入流:cin
<<是流插入运算符,>>是流提取运算符
在C++的输入输出可以自动识别变量类型
五、缺省参数
5.1缺省参数的概念
缺省参数是声明或定义函数时为函数的一个参数指定一个缺省值,在调用函数的时候,如果没有传参(也就是没有指定的实参)则采用该形参的缺省值
5.2缺省参数的分类
全缺省参数
void myq(int x=5,int y=2,int z=5)
{
cout << "x=" << x << endl;
cout << "y=" << y << endl;
cout << "z=" << z << endl;
}
半缺省参数
void fun(int x, int y = 10)
{
cout << x << y << endl;
}
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现
六、函数重载
同一个函数名可以定义为多个不同类型的函数
6.1函数重载的概念
函数重载是指在同一个作用域内,可以定义多个具有相同名称但参数列表不同的函数。C++通过函数的参数个数、类型或顺序的不同来实现函数重载。
通过使用
相同的函数名
来实现不同的功能和操作
//1.参数类型不同
int max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
double max(double x, double y)
{
if (x - y > 0)
return x;
else return y;
}
//2.参数个数不同
void max()
{
cout << "max()" << endl;
}
//3.参数类型的顺序
void min(int x, char y)
{
cout << x << y << endl;
}
void min(char x, int y)
{
cout << x << y << endl;
}
int main()
{
max(1, 4);
max(1.2, 4.2);
max();
min(6, 'm');
min('m', 5);
return 0;
}
6.2函数重载的原理----名字修饰
C/C++中,一个程序在运行中都需要经历这四个阶段:预处理、编译、汇编、链接
- 编译后链接前,a.o的目标 文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么 怎么办呢?
- 链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
- 在c中的链接,函数是通过函数名来寻找函数的地址
- 而C++中的链接,会通过函数名和函数参数的类型来寻找
七、引用
7.1引用概念
引用不是新定义一个变量,而是给已有的变量去一个别名,本质上就是同一个变量的不同叫法。
因此编译器不会为引用变量开辟内存空间,他和它引用的变量共用同一块内存空间
类型& 引用变量名(对象名) = 引用实体;
注意:
引用类型必须和引用实体为同一种类型
7.2引用特性
- 引用在定义是必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
7.3常引用
int main()
{
const int a = 10;
const int& ma = a;
//int& fa = a; //编译出错
double b = 3.14;
double & fa = b;
const int& c = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
cout << b << endl << c << endl;
return 0;
}
7.4引用的使用
参数引用
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
返回值引用
int& count()
{
static int x = 0;
x++;
return x;
}
引用函数返回值的作用
避免不必要的对象拷贝开销,提高程序的性能;支持连续赋值和链式调用,使代码更加简洁和易读;同时允许函数返回修改后的对象,增加了程序的灵活性。因此,在设计函数接口时,如果函数返回的是一个非基本数据类型的对象,考虑返回值引用通常是一个不错的选择。
7.5传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
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;
}
传值和指针在作为传参以及返回值类型上效率相差很大
7.6引用和指针的区别
语法的概念上,引用就是一个别名,没有独立空间,同引用实体共用一块空间
而在编译器实现的底层上,实际是有空间的,因为引用也是按照指针方式来实现的,只是较于c来说,把事情的重复性交给了底层,来提高效率
7.7引用与指针的不同点
1. 引用概念上定义一个变量的别名,指针存储一个**变量地址**。
2. **引用在定义时必须初始化,而指针没有要求**
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
八、内联函数
8.1内联函数概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率
这里便涉及到汇编的一些知识
内联函数实质是,将整个函数(也就是这一堆的指令) ,直接展开使用
比较 宏: 宏虽然也能直接展开函,但宏的本质是将所写的指令从一个名字中替换
8.2 特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用.
缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。 - inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到
相较于宏
的优缺点:
优:
- 可以增强代码复用性
- 提高性能
缺: - 不方便调试
- 可读性差
- 没有类型的检查
C++有哪些技术替代宏?
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
九、auto关键字
9.1类型别名思考
当程序过于复杂,类型过于繁多复杂,如
- 类型难以拼写
- 含义不明确
#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;
}
std::map<std::string, std::string>::iterator 是一个类型,但是该类型太长了,特别容易写错。
使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题:
typedef char* pstring;
int main()
{
const pstring p1; // 编译成功还是失败?
const pstring* p2; // 编译成功还是失败?
return 0;
}
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的
类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义。
9.2 auto简介
使用auto修饰的变量,是具有自动存储器的局部变量
但实际应用中并不多见,这是为什么?
:auto不再是一个存储类型指示符,而是作为一
个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
【注意】
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
9.3 auto的使用细则
- auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto* 没有区别,但用auto声明引用类型则必须加 & - 在同一行定义多个变量 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
9.4 auto不能推导的场景
- auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
- 不能直接用来声明数组
十、C++范围的for循环(C++11)
10.1范围for的语法
C++是兼容C的,所以for循环中,C的语法依然是适用的
int main()
{
for(int i=0;i<10;i++)
{
printf("%d ",i);
}
return 0;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因
此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范
围内用于迭代的变量,第二部分则表示被迭代的范围。
for(迭代变量 : 别迭代范围){}
int main()
{
int array1[] = { 1,2,3,4,5,6 };
int array2[] = { 1,2,3,4,5,6 };
//仅仅只是变量的拷贝,不做修改
for (int i : array1)
{
i *= 2;
}
for (auto e : array1)
{
cout << e << " ";
}
cout << endl;
//for范围配合引用,可以做到指针的效果
for (auto& i : array2)
{
i *= 2;
}
for (auto i : array2)
{
cout << i << " ";
}
return 0;
}
10.2范围for的使用条件
for循环迭代的范围必须是确定的
对于数组而言**,就是数组中第一个元素和最后一个元素的范围**;对于类而言,应该提供begin和end的办法,begin和end就是for循环迭代的范围
十一、nullptr指针空值(C++11)
声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
int* N = NULL;
int* M = 0;
在C的头文件中,(stddef.h)
NULL实际是一个宏 ,所以在应用中,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦
void fun(int)
{
cout << "fun(int)" << endl;
}
void fun(int*)
{
cout << "fun(int*)" << endl;
}
int main()
{
fun(0);
fun(NULL);
fun((int*)NULL);
return 0;
}
本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖.
字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入 的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr