目录
前言
C++是建立在C语言的基础之上的语言,不仅解决C语言中的一些缺陷,还增加了面向对象的编程思想和许多有用的库。C++的出现不是来替代C语言,而是C语言的“升级版”。
一命名空间
1原因
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存
在于全局作用域中,可能会导致很多冲突。如:定义的变量与函数名相同
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
2定义
C++引进了命名空间的概念。一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。(命名空间就好像张大爷家的菜地,与外界分隔开来)
3使用
命名空间的使用需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}
中即为命名空间的成员。
// 命名空间中可以定义变量/函数/类型
namespace bit
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
访问命名空间的成员有以下几种方式:
1用::(作用域限定符),前面加命名空间的名字(注意:如果前面是空格,默认访问的是全局域)
(指定张大爷家菜地(bit)的摘白菜(a)来食用)
int main()
{
printf("%d\n", bit::a);
return 0;
}
2用using和作用限定符,将命名空间的成员引入
(指定从张大爷家的菜地(bit)的黄瓜(b)来食用)
using bit::b;
int main()
{
printf("%d\n", b);
return 0;
}
3用using newspace 命名空间的名字引入
(使用张大爷家的菜地(bit)中的任何蔬菜来食用)
using namespce bit;
int main()
{
printf("%d\n", b);
Add(10, 20);
return 0;
}
二缺省参数
1概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
cout<<a<<endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
2分类
1全缺省
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
2半缺省
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
注意:1半缺省从右到左连续给值,中间不能隔开 ,因为是语法规定死的。
2缺省参数不能在函数声明和定义中同时出现。会引发歧义,编译器会不知道用哪个缺省参数来使用(报错:重定义默认参数)。
3缺省参数必须是常量或者局部变量。
下面给出缺省参数的一个应用场景:栈的初始化开多少空间的问题
struct Stack
{
int* a;
int size;
int capacity;
};
void StackInit(struct Stack* ps, int n = 4);//在函数的声明中使用缺省参数
void StackInit(struct Stack* ps, int n)//而在函数的定义不用再重复定义缺省参数
{
ps->a = NULL;
ps->a = (int*)malloc(sizeof(int) * n);
//...
}
int main()
{
struct Stack s1;
//确定要插入100个数据
StackInit(&st1, 100);
//不知道要插入多少个数据就不传值
struct Stack st2;
StackInit(&st2);
return 0;
}
三函数重载
1概念
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题。
//1、参数类型不同
int Add(int left, int right);
double Add(double left, double right);
//2、参数个数不同
void f();
void f(int a);
//3、参数类型顺序不同
void f(int a, char b);
void f(char b, int a);
2原因
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
在最后一步的链接中,编译器要通过函数名来找到函数地址进行最后的合并。在C语言中为了防止发生函数名相同而找不到函数地址,所以不允许出现同名函数。
但在日常做项目时,不可避免会发生同名函数出现的问题(毕竟一个项目不一定时同个人完成的)。为了解决这个问题,在C++中,出现了函数重载这一概念。但C++对于函数名的修饰规则没有明确的规定。在不同的编译器中修饰规则大不相同。
由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使
用了g++演示了这个修饰后的名字。
通过上面,我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度
+函数名+类型首字母】
四引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。(但从汇编层面下,引用是会开辟内存空间的)
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
1语法
类型& 引用变量名(对象名) = 引用实体
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
}
引用与实体的类型必须得相同!!
2特性
1引用在定义是必须初始化
2一个变量可以有多个引用
3引用一旦引用一个实体,再不能引用其他实体
int main()
{
int a=0;
int& b;//编译报错
int&c=a;
int&d=a;
int e=2;
c=e;//不是对e的引用,而是赋值
return 0;
3使用
a作参数:
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
//与指针作对比
void Swap(int* left, int* right)
{
int temp = *left;
*left = *right;
*right = temp;
}
b作返回值:
fuc()函数返回的类型是int&,但变量a在出函数后a的空间销毁,返回的实际上是a的地址
但vs在处理时把a的空间保留了下来。
与以下代码作对比:
Fuc函数返回的类型时int,int a出函数之前将数据拷贝到寄存器中拷贝给ret
总结:返回变量出来函数要摧毁局部变量,返回值的类型不能是引用返回
全局变量,静态变量,堆上变量(如:malloc)得变量就可以引用返回
c传值调用VS传引用调用
#include <time.h>
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;
}
传引用调用不开栈帧直接展开调用,提高效率减少拷贝
4指针与引用
引用在语法上是不开空间的,与引用的对象共用一块空间。
而指针要开空间存储地址。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
引用和指针的比较:
1. 引用不开空间,指针开空间
2. 引用在定义时必须初始化,指针没有要求
3. 引用不能改变指向,而指针可以
4. 引用相对更安全,指针有野指针
6.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
7.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小8. 有多级指针,但是没有多级引用
9. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
5常引用
int a=10;
const int& b=a;
用const来修饰int&,可以:使用引用权限缩小编译器时支持的
但反过来行吗?
const int a=10;
//int& b=a;
int c=20,d=30;
//int& e=c+d;
const int& e=c+d;
double f=1.1;
//int& g=f;
const int& g=f;
不行!不允许权限放大
那么,表达式可不可以? 也是不行,表达式(类型转换)返回的是临时变量(临时变量具有常性)
函数传参也是一样的:
void fuc(int& b)
int main()
{
int a=10;
fuc(a);//可以
fuc2(10);//不可以,权限放大
}
五内联函数
1概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
与不加inline作对比:
VS下:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下编译器默认不
会对代码进行优化,以下给出设置方式)
2特性
1.inline是一种以空间(目标文件的大小)换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、而是频繁调用的函数采用inline修饰。否则即使有inline,编译器也不展开!3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了(函数地址未进符号表),链接就会找不到所以:函数重定义的解决问题:
大函数:1函数声明与定义分开 2static修饰(改变链接属性,仅在当前文件可见)
小函数:inline修饰
3宏
在C语言中,我们要解决频繁调用的小函数,提高效率,用宏来实现。
#include<stdio.h>
#define Add(a, b) ((a)+(b))
int main()
{
int ret=Add(1,2);
printf("%d",ret);
}
在用宏来定义函数,坑很多(函数参数不用加类型,不用return返回等等) ,也不方便阅读。
但这个恰恰可以用来证明C语言学的扎不扎实的体现:
【面试题】
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数
六auto关键字
1概念
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
注意:使用auto定义变量时必须对其进行初始化
2使用
auto与指针和引用结合起来使用
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
在同一行定义多个变量
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
3常见错误
auto不能作为函数的参数:
void TestAuto(auto a)
{
retrun a;
}
//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
auto不能直接用来声明数组
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
嵌套使用变量慎用auto
....
auto f2()
{
auto ret = f3();
return ret;
}
auto f1()
{
auto x = f2();
return x;
}
auto TestAuto()
{
auto a = f1();
return a;
}
如上所示,要想直到a的类型是什么,要根据变量往上进行推导。如果一个函数有几百行代码量,要想直到a的变量时不容易的...
七指针空值(nullptr)
#include<iostream>
using namespace std;
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* p)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
在上面的代码中,程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但为什么会出现这样呢?
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可以是字面常量0,也可以是无类型指针(void*)的常量。
在上面的代码中,编译器把NULL看成时常量0了。
改进:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f((int*)NULL);//强制的类型由函数的参数决定
f(nullptr);//或者直接使用指针空值
return 0;
}
注意:
1. 在使用nullptr表示指针空值时不需要包含头文件,因为nullptr是C++11作为新关键字引的。2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
最后
C++入门要学习的语法大概就是以上的内容,使用的注意事项要理解掌握,成为学习C++的一块“入门砖”!