目录
3.多⽂件中可以定义同名namespace,他们会默认合并到⼀起,就像同⼀个namespace⼀样
什么是C++
C++(c plus plus)是一种计算机高级程序设计语言,由C语言扩展升级而产生,最早于1979年由本贾尼·斯特劳斯特卢普在AT&T贝尔工作室研发。C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计。
C++的发展史
20世纪70年代中期,本贾尼·斯特劳斯特卢普在剑桥大学计算机中心工作。斯特劳斯特卢普希望开发一个既要编程简单、正确可靠,又要运行高效、可移植的计算机程序设计语言。而以C语言为背景,以Simula思想为基础的语言,正好符合斯特劳斯特卢普的初衷和设想。1979年,本贾尼·斯特劳斯特卢普到了AT&T贝尔实验室,开始从事将C改良为带类的C(C with classes)的工作。、1983年,该语言被正式命名为C++。让我们来看看C++的历史版本。
命名空间
在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全 局作⽤域中,可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名 冲突或名字污染,namespace关键字的出现就是针对这种问题的。
特别注意C++标准库都放在⼀个叫std(standard)的命名空间中。
命名空间的定义
定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中 即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
1.正常的命名空间定义
//1.正常的命名空间
namespace yxt
{
int rand = 10;
int ADD(int left, int right)
{
return left + right;
}
struct Node
{
int val;
struct Node* next;
};
}
2.命名空间可以嵌套
//2.命名空间可以嵌套
namespace yxt
{
namespace a1
{
int rand = 1;
int ADD(int left, int right)
{
return left + right;
}
}
namespace a2
{
int rand = 2;
int ADD(int left, int right)
{
return (left + right)*10;
}
}
}
3.多⽂件中可以定义同名namespace,他们会默认合并到⼀起,就像同⼀个namespace⼀样
//Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace yxt
{
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n);
}
//Stack.cpp
#include "Stack.h"
namespace yxt
{
void STInit(ST* ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
}
命名空间的使用
程序编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:
1.指定命名空间访问
2.using将命名空间中的某个成员展开
3.展开命名空间中的全部成员
符号"::"在C++中叫做域作用限定符,我们可以通过 “命名空间名称::命名空间成员”来访问到命名空间中相对应的成员。
1.指定命名空间访问
//指定命名空间访问
namespace yxt
{
int a = 1;
int b = 2;
}
int main()
{
printf("%d\n", yxt::a);
return 0;
}
2.using将命名空间中的某个成员展开
//using将命名空间中的某个成员展开
namespace yxt
{
int a = 1;
int b = 2;
}
using yxt::b;
int main()
{
printf("%d\n", yxt::a);
printf("%d\n", b);
return 0;
}
3. 展开命名空间中的全部成员
//展开命名空间中的全部成员
namespace yxt
{
int a = 1;
int b = 2;
}
using namespace yxt;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
C++的输入与输出
在C语言中由标准输入输出函数scanf和printf,而在C++中有与之对应的标准输入流cin和标准输出流cout。使用时需包含头文件iostream以及通过命名空间std来使用他们。
相比C语言,C++的输入输出更加方便,因为其输入输出不需要控制格式,可以自动的识别变量的类型。
#include<iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c << std::endl;
// 可以⾃动识别变量的类型
cin >> a >> b >> c;
cout << a << " " << b << " " << c << endl;
return 0;
}
此处的endl是一个函数, 流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区,作用相当于换行。
缺省参数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参 则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有时也叫默认参数)
void func(int a = 0)
{
cout << a << endl;
}
int main()
{
func(); //没有传参时,默认使用参数的默认值
func(10); //传参时,使用指定的实参
return 0;
}
全缺省
全缺省就是全部形参给缺省值
//全缺省
void func1(int a = 10, int b = 20, int c = 30)
{
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);
return 0;
}
半缺省
半缺省就是部分形参给缺省值
//半缺省
void func2(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
func2(1);
func2(1, 2);
func2(1, 2, 3);
return 0;
}
缺省参数有以下注意的点:
1.C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
2.带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
3.函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
//Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n = 4);
//Stack.cpp
#include "Stack.h"
void STInit(ST* ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
//test.cpp
#include"Stack.h"
int main()
{
ST s1;
STInit(&s1);
//如果已经确定要插入500个数据,初始化时,直接开好空间,避免扩容。
ST s2;
STInit(&s2, 500);
return 0;
}
函数重载
C++⽀持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。
若只有返回值不同,其余均相同,则不构成函数重载。
//1.参数类型不同
int ADD(int x, int y)
{
return x + y;
}
double ADD(double x, double y)
{
return x + y;
}
//2.参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
//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;
}
引用
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间, 它和它引⽤的变量共用同⼀块内存空间。
类型& 引⽤别名 = 引⽤对象;
int main()
{
int a = 1;
//引用:b和c是a的别名
int& b = a;
int& c = a;
//也可以给别名起别名,相当于d还是a的别名
int& d = b;
//改变d相当于改变a
++d;
cout << a << endl;
//取地址后我们发现地址均相同
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
引用的特性
1.引用在定义时必须初始化。
2.一个变量可以有多个引用。
3.引用一旦引用一个实体,再不能引用其他实体。 (引用不能改变指向)
const引用
可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访 问权限在引⽤过程中可以缩⼩,但是不能放⼤。
int main()
{
const int a = 10;
int& ra = a;
//编译会报错,因为a本身为常量只可读不可写,这样引用使得对a访问权限放大
//正确的是
const int& ra = a;
//这里的引用是对b访问权限的缩小
int b = 20;
const int& rb = b;
return 0;
}
下面我们来看这么一段代码
int main()
{
double b = 1.23;
int& rb = b;
return 0;
}
第一眼可能我们认为是对的,但结果是代码会报错。想搞明白其中的原因,我么要知道隐式类型转换。从double变为int的过程中会发生隐式类型转换,在这个过程中编译器会产生一个空间来临时存储中间值(也叫临时对象),而C++中规定临时对象具有常性,所以这里触发了权限放大,必须用常引用来解决。
int main()
{
double b = 1.23;
const int& rb = b;
return 0;
}
指针和引用的关系
1.语法概念上引用是一个变量的别名,不额外开辟空间,指针存储一个变量的地址,额外开辟空间。
2.引用在定义时必须初始化,指针则不是必须初始化。
3.引用在初始化引用一个对象后,就不能引用其他对象;指针可以不断改变指向对象。
4.引用可以直接访问引用对象;指针需要解引用才能访问指向对象。
5.指针很容易出现空指针和野指针的现象;引用很少出现,相对更安全。
6.sizeof中含义不同,引用结果为引用类型的大小;指针是地址空间所占字节数(32位平台下是4个字节,64位平台下是8个字节)。
内联函数(inline)
⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调⽤内联函数就需要建⽴栈帧了,就可以提⾼效率。
看到展开函数,我们可能会下意识的想到C语言中的宏函数。
C语⾔实现宏函数会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调 试,C++设计了inline⽬的就是替代C的宏函数。
inline int ADD(int x, int y)
{
int ret = x + y;
return ret;
}
int main()
{
int ret = ADD(1, 2);
return 0;
}
内联函数的使用
1.inline是一种以空间换时间的做法,因此对编译器来说只是一个建议,不同编译器关于inline什么情况展开各不相同。 inline适用于频繁调用的短小函数。对于递归函数或者代码相对长一点的函数来说,即使加上inline也会被编译器忽视。
2.inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为在inline被展开时,就没有了函数地址,链接时会报错。
指针空值(nullptr)
在C语言中,我们常常用到NULL,NULL其实是一个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到NULL可能被定义为常量0,或者定义为(void*)类型的常量0。不管使用哪种定义,都会不可避免的遇到麻烦,比如:
void fun(int x)
{
cout << "fun(int)" << endl;
}
void fun(int* ptr)
{
cout << "fun(int*)" << endl;
}
int main()
{
fun(0); //结果为fun(int)
fun(NULL); //结果为fun(int)
fun((int*)NULL); //结果为fun(int*)
return 0;
}
在fun(NULL)中,本意是调用指针版本的fun(int* ptr),但由于NULL被定义为0,最终调用的是fun(int x)。
为了解决这个问题,C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型 。