C++ 基础语法
目录一、编译环境 二、头文件三、命名空间1.命名空间的定义2.命名空间的访问四、输入与输出五、内存分配六、引用1.常引用2.引用的使用 3.指针和引用的区别 七、函数重载 八、缺省参数 九、内联函数在虚拟机Ubuntu上使用g++,g++是Linux中C++的编译器,需要安装,g++的使用方法和gcc一样。C和C++的关系:C是C++的基础,C++是C的扩展,C++的类型检查比C严格,C++完全兼
目录
一、编译环境
在虚拟机Ubuntu上使用g++,g++是Linux中C++的编译器,需要安装,g++的使用方法和gcc一样。
测试系统是否有g++: g++ -v
安装:
sudo apt-get update
sudo apt-get install g++
C和C++的关系:C是C++的基础,C++是C的扩展,C++的类型检查比C严格,C++完全兼容C
扩展的内容:
1.面向对象(以类的方式组织代码)
2.运算符的重载(一种特殊的函数形式)
3.异常(一种全新的错误处理方法)
4.泛型编程(类型通用的实现)
二、头文件
C++包含自定义的头文件时不再以.h作为结尾
#include <iostream>//支持输入输出的头文件
#include <string>
#include <list>
但是C语言的标准头文件任然可用
#include <stdio.h>
#include <stdlib.h>
一般C标准头文件在C++引用使用如下方式
#include <cstdio>
#include <cstdlib>
注意,非标准头文件不支持该语法。自定义头文件和C语言一样引用。
三、命名空间
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称都将作用于全局作用域中,可能会导致很多命名冲突。命名空间的作用是对标识符和名称进行本地化,以避免命名冲突或名字污染。
1.命名空间的定义
定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
namespace 空间名{
变量的声明和定义;
函数的声明和定义;
.....
}
同一个工程中允许存在多个相同名称的命名空间,编译器最后会将其成员合成在同一个命名空间中,所以不能在相同名称的命名空间中定义两个相同名称的成员。
注意:一个命名空间就定义了一个新的作用域,命名空间中所有内容都局限于该命名空间中。
2.命名空间的访问
①加命名空间名称及作用域限定符
符号“::”在C++中叫做作用域限定符,通过“命名空间名称::命名空间成员”便可以访问到命名空间中相应的成员。
②使用using将命名空间中的成员引入
通过“using 命名空间名称::命名空间成员”的方式将命名空间中指定的成员引入。在该语句之后的代码中就可以直接使用引入的成员变量了。
③使用using namespace 命名空间名称引入
通过”using namespace 命名空间名称“将命名空间中的全部成员引入。在该语句之后的代码中就可以直接使用该命名空间内的全部成员了。
匿名命名空间(不检验使用):如果一个数据不属于任何命名空间,C++编译器就会将其加入到匿名命名空间,可以使用以下语法来访问匿名命名空间中的内容
::内容;
定义匿名命名空间
namespace
{
内容;
}
命名空间的嵌套 :
namespace ns1{
xxx;
namespace ns2{
yyy;
namespace ns3{
zzz;
}
}
};
访问:
ns1::xxx;
ns1::ns2::yyy;
ns1::ns2::ns3::zzz;
四、输入与输出
在C语言中有标准输入输出函数scanf和printf,而在C++中有cin标准输入和cout标准输出。在C语言中使用scanf和printf函数,需要包含头文件stdio.h。在C++中使用cin和cout,需要包含头文件iostream以及std标准命名空间。
#include <iostream> //引用输入输出流文件
using namespace std; //引用std标准命名空间
标准输出:cout: cout<<"要输出的内容"<<endl;
可以输出任何非自定义的数据,endl的作用:换行符
使用cout<<xxx<<yyy<<zzz<<endl; //进行连续输出
标准输入: cin:cin>>代表空间的内容(变量/地址);
使用cin>>xxx>>yyy>>zzz; //进行连续输入
若没有引用std标准命名空间,需要加上std的域名
std::cout<<xxx<<endl;
C++的输入输出方式与C语言相比是更加方便的,因为C++的输入输出不需要增加数据格式控制,如:整型为%d,字符型为%c。
五、内存分配
在C语言堆内存的管理
malloc/free
calloc ----------- 分配的内存会自动初始化为0
realloc ---------- 调整分配的堆内存的大小
C++堆内存的管理
new/delete -------- 分配和释放单个内存
new[] / delete[] ---- 分配和释放多个连续内存
①申请和释放一个类型大小的空间:
类型名 *指针变量名 = new 类型; //申请一个类型大小的空间
类型名 *指针变量名 = new 类型(初始值); //分配同时初始化
delete 指针变量名; // 释放一个类型大小的空间
②申请和释放多个同类型大小的连续空间
类型名 *指针变量名 = new 类型[n]; //n表示该类型大小的个数
delete[] 指针变量名; //释放多个同类型大小的连续空间
“()”小括号里面的值表示内存空间的初始值,“[ ]”中括号里面的值表示该类型大小的个数。
注意:new/delete和malloc/free的区别在于申请和释放对象内存时,在C++中不使用malloc/free,而使用new/delete。
六、引用
引用不是定义一个变量,而是为已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
其使用的基本形式为:
类型& 引用变量名(对象名) = 引用实体;
注意:引用在定义时必须初始化,并且引用类型必须和引用实体是同种类型。 一个变量可以有多个引用。引用一旦引用了一个实体,就不能再引用其他实体,引用一旦初始化,引用的对象不能改变。
//正确
int a = 10;
int& b = a;//引用在定义时必须初始化
//错误
int c = 10;
int &d;//定义时未初始化
d = c;
1.常引用
引用类型必须和引用实体是同种类型的,但是仅仅是同种类型,还不能保证能够引用成功,如果用一个普通引用类型去引用其对应的类型,但该类型被const所修饰,那么引用将不会成功。
int main()
{
const int a = 10;
//int& ra = a; //该语句编译时会出错,a为常量
const int& ra = a;//正确
//int& b = 10; //该语句编译时会出错,10为常量
const int& b = 10;//正确
return 0;
}
可以将被const修饰的类型理解为安全的类型,因为其不能被修改。若将一个安全的类型交给一个不安全的类型(可被修改),那么将不会成功。常量的引用必须用const修饰。
2.引用的使用
①引用做参数
在C语言中交换函数常用交换函数来说明传值和传址的区别。在C++中可以不用指针作为形参,使用引用作为参数。
//交换函数
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
a和b是传入实参的引用,将a和b的值交换,也就将传入的两个实参交换。普通变量作为函数的参数只能实现值的单向传递,引用作为函数参数可以实现值的双向传递。
②引用做返回值
引用做返回值时,返回的数据不能是函数内部创建的普通局部变量。因为在函数内部定义的普通的局部变量会随着函数调用的结束而被销毁。返回的数据必须是被static修饰或者动态开辟或者全局变量不会随着函数调用的结束而被销毁的数据。
int& Add(int a, int b)
{
static int c = a + b;
return c;
}
一般函数的返回值只能作为右值,如果希望函数的返回值作为左值就可以使用引用类型。
③指针引用
引用的地址就是原数据的地址,指针的引用就是指针的别名,通常就是用来代替二级指针。
int *p = NULL;
int *&rp = p; //指针的引用,相当于一个二级指针
④数组引用
数组的引用作为函数参数可以保留数组的特性,但是同时丧失了数组传参的灵活性,基本不使用。
int arr[5];
int (&r_arr)[5] = arr;//数组的引用
void set_arr(int (&r_arr)[5])
{
sizeof(r_arr) ===> 20,整个数组的大小
}
void set_arr(int a[],int n)
{
sizeof(a) ===> 4,指针的大小
}
3.指针和引用的区别
在语法概念上,引用是一个别名,没有独立的空间,其和引用实体共用同一块空间。
int main()
{
int a = 10;
//在语法上,给a这块空间取了一个别名,没有新开空间
int& ra = a;
ra = 20;
//在语法上,这里定义了一个pa指针,开辟了4个字节的空间,用于存储a的地址
int* pa = &a;
*pa = 20;
return 0;
}
但是在底层实现上,引用实际是有空间的
从汇编角度来看,引用的底层实现也是类似指针存地址的方式来处理的。
引用和指针的区别(重要):
①引用在定义时必须初始化,指针没有要求。
②引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
③没有NULL引用,但有NULL指针。
④在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
⑤引用进行自增操作就相当于实体增加1,而指针进行自增操作是指针向后偏移一个类型的大小。
⑥有多级指针,但是没有多级引用。
⑦访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理。
⑧引用比指针使用起来相对更安全。
七、函数重载
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,但是同名函数的形参列表必须不同。函数重载常用来处理实现功能类似,而数据类型不同的问题。
形参列表不同:
1.参数个数不同
2.参数类型不同
3.参数顺序不同
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
return 0;
}
重载函数的调用:调用重载函数时,根据传递的实参来选择合适的函数调用。因为C++编译函数时不但记录函数名,还会记录参数列表。函数调用时不但匹配函数名,还匹配参数列表 。
函数重载的意义:对于类似功能,在不同的数据下可以用一个函数名实现,大大简化了上层调用的复杂度。
哑元:如果一个形参只有类型,没有形参名,这种参数就叫哑元。
int print(int/*哑元*/)
{
//......
}
哑元的作用就是实现参数没有区别的函数的重载。
那么为什么C语言不支持函数重载?这是由于C++编译器和C编译器对函数的处理有区别。
1.C++中参数列表为()表示没有参数,等同于(void) ,但是在C中()表示任意数据类型。
2.C++不支持隐式声明,必须前置声明,函数调用时必须声明
3.C++的函数不再默认返回int,必须指定返回值类型
4.C++的函数调用匹配不但匹配函数名,还匹配参数列表,所以c++支持函数重装 ,而C只匹配函数名 (在源文件-->汇编)
在编译阶段会将程序中的每个源文件的全局范围的变量符号分别进行汇总。在汇编阶段会给每个源文件汇总出来的符号分配一个地址(若符号只是一个声明,则给其分配一个无意义的地址),然后分别生成一个符号表。最后在链接期间会将每个源文件的符号表进行合并,若不同源文件的符号表中出现了相同的符号,则取合法的地址为合并后的地址(重定位)。
在C语言中,汇编阶段进行符号汇总时,一个函数汇总后的符号就是其函数名,所以当汇总时发现多个相同的函数符号时,编译器便会报错。而C++在进行符号汇总时,对函数的名字修饰做了改动,函数汇总出的符号不仅有函数的函数名还有其参数的类型和个数以及顺序等信息汇总出 一个符号。所以就算是函数名相同的函数,只要其参数的类型或参数的个数或参数的顺序不同,那么汇总出来的符号也就不同了。
八、缺省参数
缺省参数是指在声明或定义函数时,为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
返回值类型 函数名(形参类型1 形参名1,...,形参类型n 形参名n=默认值)
{
//......
}
全缺省参数:即函数的全部形参都设置为缺省参数。
半缺省参数:即函数的参数不全为缺省参数。
注意:
①半缺省参数必须从右往左依次给出,有默认值的参数必须靠右,一个参数有默认值,其右边的参数必须有默认值。
void xxx(int a=10,int b); //错误,缺省参数必须靠右
②缺省参数只能在函数声明时出现,或者函数定义时出现。
③缺省值必须是常量或者全局变量
④使用参数默认值时需要注意不要和函数重载相冲突
int add(int a,int b);
int add(int a,int b,int c=100); //冲突
函数参数的默认值的意义:减少传参的个数,提高函数调用的效率 方便函数的调用。
九、内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数的使用可以提升程序的运行效率。
int Add(int a, int b) //普通函数
{
return a + b;
}
inline int Add(int a, int b) //内联函数
{
return a + b;
}
int main()
{
int ret = Add(1, 2);
return 0;
}
下图左是以正常函数调用的汇编代码,下图右是函数加上inline后的汇编代码
从汇编代码中可以看出,内联函数调用时并没有调用函数这个过程的汇编指令,而是直接展开。
内联函数的特性
①inline是一种以空间换时间的做法,省了去调用函数的额外开销。由于内联函数会在调用的位置展开,所以代码很长或者有递归的函数不适宜作为内联函数。频繁调用的小函数建议定义成内联函数。
②inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有递归等,编译器优化时会忽略掉内联。
③inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了链接就会找不到。
更多推荐
所有评论(0)