相较于C语言,C语言有32个关键字,而C++则有63个关键字
在C/C++中,变量、函数等的类都是大量存在的,这些变量、函数和类的名称将都存
在于全局作用域中,可能会导致很多冲突。
使用命名空间的目的是对标识符的名称进行本地化,
以避免命名冲突或名字污染,**namespace
**关键字的出现就是针对这种问题的
命名空间的普通定义
//命名空间的普通定义
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;
}
}
}
同一个命名空间的合并
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
作用域:
符号**“::”**在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();
}
std是C++标准库的命名空间名,C++将标准库的定义实现都放在这个命名空间中
使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。
输出流:cout和endl,其中endl表示换行输出.他们都包含在的头文件中 输入流:cin
<<是流插入运算符,>>是流提取运算符
缺省参数是声明或定义函数时为函数的一个参数指定一个缺省值,在调用函数的时候,如果没有传参(也就是没有指定的实参)则采用该形参的缺省值
全缺省参数
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;
}
同一个函数名可以定义为多个不同类型的函数
函数重载是指在同一个作用域内,可以定义多个具有相同名称但参数列表不同的函数。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;
}
C/C++中,一个程序在运行中都需要经历这四个阶段:预处理、编译、汇编、链接
引用不是新定义一个变量,而是给已有的变量去一个别名,本质上就是同一个变量的不同叫法。
因此编译器不会为引用变量开辟内存空间,他和它引用的变量共用同一块内存空间
注意:
引用类型必须和引用实体为同一种类型
- 引用在定义是必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
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;
}
参数引用
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
返回值引用
int& count()
{
static int x = 0;
x++;
return x;
}
引用函数返回值的作用
避免不必要的对象拷贝开销,提高程序的性能;支持连续赋值和链式调用,使代码更加简洁和易读;同时允许函数返回修改后的对象,增加了程序的灵活性。因此,在设计函数接口时,如果函数返回的是一个非基本数据类型的对象,考虑返回值引用通常是一个不错的选择。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
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;
}
语法的概念上,引用就是一个别名,没有独立空间,同引用实体共用一块空间
而在编译器实现的底层上,实际是有空间的,因为引用也是按照指针方式来实现的,只是较于c来说,把事情的重复性交给了底层,来提高效率
1. 引用概念上定义一个变量的别名,指针存储一个**变量地址**。
2. **引用在定义时必须初始化,而指针没有要求**
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率
这里便涉及到汇编的一些知识
内联函数实质是,将整个函数(也就是这一堆的指令) ,直接展开使用
比较 宏: 宏虽然也能直接展开函,但宏的本质是将所写的指令从一个名字中替换
相较于宏
的优缺点:
优:
C++有哪些技术替代宏?
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
当程序过于复杂,类型过于繁多复杂,如
#include
#include
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
使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题:
typedef char* pstring;
int main()
{
const pstring p1; // 编译成功还是失败?
const pstring* p2; // 编译成功还是失败?
return 0;
}
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的
类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义。
使用auto修饰的变量,是具有自动存储器的局部变量
但实际应用中并不多见,这是为什么?
:auto不再是一个存储类型指示符,而是作为一
个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
【注意】
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
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;
}
for循环迭代的范围必须是确定的
对于数组而言**,就是数组中第一个元素和最后一个元素的范围**;对于类而言,应该提供begin和end的办法,begin和end就是for循环迭代的范围
声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
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