目录
1、关键字
C++总计63个关键字,C语言32个关键字
2、命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
printf("%d\n", rand);
return 0;
}
编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”。
2.1命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{ } 中即为命名空间的成员。
// wh是命名空间的名字
// 1. 正常的命名空间定义
namespace wh
{
// 命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
//2. 命名空间可以嵌套
// test.cpp
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
// ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
// test.h
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
2.2命名空间的使用有三种方式
- 加命名空间名称及作用域限定符 (指定访问,不常用的使用指定访问)
int main()
{
printf("%d\n", N::a);
return 0;
}
- 使用using将命名空间中某个成员引入(指定展开,常用的使用指定展开)
using N::b; int main() { printf("%d\n", N::a); printf("%d\n", b); return 0; }
- 使用using namespace 命名空间名称 引入(全展开)
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
2.3域
局部域(影响生命周期)
全局域(影响生命周期)
命名空间域(全局的,不会影响生命周期)
都可以做到名字的隔离,不同域,可以定义同名的变量、函数、类型。
编译器默认查找顺序:
a.局部
b.全局
c.命名空间
b和c是同时进行的。如果有命名冲突,就不需要展开命名空间。
3、 C++输入&输出
输出‘Hello world!!!’
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
说明:
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
3. >是流提取运算符。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入输出可以自动识别变量类型。
5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,这里只是初识。
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应 头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间, 规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因 此推荐使用+std的方式。
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
// 可以自动识别变量的类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢?
1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模 大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。
4、缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
缺省:形参赋值。
//全缺省(所有形参赋值)
void Fun2(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
//半缺省(有的形参不赋值,根据传参的顺序,缺省只能从右开始)
void Fun3(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
//Fun(2);//给a传2,输出为2
//Fun();//不给a传值,输出1,a的默认参数。
Fun2(1, 2, 3);
Fun2(1, 2);
Fun2(1);
Fun2();
Fun3(1);
Fun3(1,2);
Fun3(1,2,3);
return 0;
}
注意:必须顺序传值,不能跳跃传值。
5、函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题。
为什么c语言不支持函数重载:
c语言直接用函数名链接的时候去查找,c++用修饰后的函数名连接查找。
#include<iostream>
using namespace std;
// 1、参数类型不同
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;
}
// 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()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修 饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办 法区分。
6、引用
6.1引用概念
引用给变量取了一个别名,不开辟内存空 间,它和它引用的变量共用同一块内存空间
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
注意:引用类型必须和引用实体是同种类型,引用++ra,a也变化,ra和a的地址相同。
6.2引用特征
1. 引用必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
#include<iostream>
void TestRef()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
6.3常引用
权限可以缩小可以平移,不可以放大。(仅对于指针和引用,值是一种拷贝)
6.4引用使用场景
1.做参数(提高效率)
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
a是x的别名,b是y的别名。 👆
指针也可以引用. 👆
类型转换需要临时变量
double ---> int 类型转换,需要一个临时变量来存放double数据,然后取整形的值给给r。(临时变量是不能修改的,所以int& r要想引用,必须加const,否则就是权限放大)
x+y的值也需要放在临时变量。
引用和指针的区别
· 语法上:引用不开空间,申请别名;指针在语法上要开空间,存的是地址。
· 引用必须初始化,指针没要求
· 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
· 没有NULL引用,但有NULL指针
· 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终时地址空间所占字节个数(32位平台下占4个字节)
· 引用自加即引用的实体增加,指针自加即指针向后偏移一个类型的大小。
2.做返回值(暂时不写)
6.5引用知识点
A.引用必须初始化,必须在定义引用时明确引用的是哪个变量或者对象,否则语法错误,指针不初 始化时值为随机指向
B.引用一旦定义时初始化指定,就不能再修改,指针可以改变指向
C.引用必须出示化,不能出现空引用,指针可以赋值为空
D.简单粗暴的引用理解可以理解为被引用变量或对象的"别名"
E.引用表面好像是传值,其本质也是传地址,只是这个工作有编译器来做,所以错误
F.函数调用为了提高效率,常使用引用或指针作为函数参数传递变量或对象
G.指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作
H.空指针没有任何指向,删除无害,引用是别名,删除引用就删除真实对象
7、内敛函数
c->宏函数
#define ADD(a,b) ((a) + (b))
为什么最后不加分号?
在c中,
int main()
{
int ret = ADD(1,2); // int ret = ADD(1,2);;;;; 加多少个分号都可以
cout << ADD(1,2) <<endl; //ADD这里就不能加分号
int x = 1,y = 2;
ADD(x & y , x | y);//为什么要加内括号:为了保证优先级正确。
return 0;
}
为什么要加分号
为什么要加外括号:当给ADD(1,2)乘一个数的时候
为什么要加里面的括号:符号优先级,当传入比+优先级低的运算符就会出错,比如& 和 |
ADD(x & y , x | y);
7.1内敛
对于经常调用的小函数。
inline int Add(int a,int b)
{
int ret = a + b;
return a + b;
}
int main()
{
int c = Add(1,2);//内敛函数Add,函数直接在这里展开.
cout << c << endl;
return 0;
}
Debug版本下面默认不展开 --> 便于调试
relase版本太超前,不建议反汇编。
7.2内敛函数特性
·inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用
·函数规模较小、不是递归、且频繁调用的函数
·不建议声明和定义分离
7.3内敛函数知识点
· 使用inline关键字的函数会不会被编译器在调用处展开
不一定,因为inline只是一种建议,需要看此函数是否能够成为内联函数
· 头文件中可以包含inline函数的声明
inline函数不支持声明和定义分离开,因为编译器一旦将一个函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,也不能在其他源文件中调用,故一般都是直接在源文件中定义内联函数的
· 可不可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数
inline函数会在调用的地方展开,所以符号表中不会有inline函数的符号名,不存在链接冲突。
· 递归函数是不是都可以成为inline函数
比较长的函数,递归函数就算定义为inline,也会被编译器忽略
8、auto关键字
8.1auto自动推导数据类型
适合替换长类型(比较长的类型的定义,简化代码)
不能定义数组。
void main()
{
int a = 0;
auto a = 0;//根据右侧,auto自动推导类型。
}
和typedef有相似的类型。
typedef的问题
typedef char* pstring; //字符指针类型pstring
int main()
{
const pstring p1; //声明一个pstring类型的常量指针,
//因为指针本身是可以修改的,const修饰不能修改
const pstring* p2;// 不能通过p2指向的指针来修改它指向的数据,
//但可以通过p2来改变他的指针
return 0;
}
typeid作用:识别当前类型
8.2auto语法
遍历数组
int main()
{
int array[] = { 1,2,3,4,5 };
//普通遍历方法
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << " ";
}
cout << endl;
//auto e遍历方法
//自动取数在array,赋值给e
//自动++,自动判断结束
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
}
e是数组array数据的临时拷贝,修改e不会改变数组内容,若想改变,则需要改为:auto& e
auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编
译期会将auto替换为变量实际的类型。
与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环
8.3auto使用细则
1.auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有区别,但用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;
}
2.在同一行定义多个变量
在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译
器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
8.4auto不能使用场景
1.auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{ }
2.auto不能直接用来声明数组
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
9、指针空值nullptr(C++11)
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下
方式对其进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL实际是一个宏,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
在这里我们可以看到,0为int类型,NULL为int类型,如果NULL要想变成int*类型 ,必须强制类型转化。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
本集完。