C++基础知识
目录
前言:
C 语言是结构化和模块化的语言,适合处理较小规模的程序;对于复杂的问题,规模较大 程序,需要高度的抽象和建模时, C 语言则不合适;为了解决软件危机, 20 世纪 80 年代, 计算机界提出了 OOP(object oriented programming:面向对象)思想 ,支持面向对象的程序设计语言 应运而生;1982 年, Bjarne Stroustrup博士 在 C 语言的基础上引入并扩充了面向对象的概念,发明了一 种新的程序语言。为了表达该语言与 C 语言的渊源关系,命名为 C++ ,因此: C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计;
命名空间
由于变量 函数 类的名称存在于全局作用域中,会导致命名冲突;
使用命名空间的目的是对标识符的名称进行本地化,以防止命名冲突或名字污染;
# include <stdio.h>
# include <stdlib.h> //stdlib.h文件中包含库函数rand()函数
int rand = 20; //全局变量rand与stdlib.h文件中的库函数rand()发生命名冲突
int main()
{
printf("%d\n", rand);
return 0;
}
//代码运行结果 error C2365:"rand":重定义,以前定义是"函数"
命名冲突的场景:
1.? 程序员定义的变量与库函数名相冲突;
2.? 多人协作同一个大型项目,程序员之间发生函数名命名冲突;
命名空间的定义
程序设计者根据需要指定一些带有名字的空间域,将一些全局实体分别存放于各个命名空间中,从而与其他全局实体分割出来;
定义命名空间的关键字:namespace
语法: namespce 命名空间名 { 命名空间成员 }
//命名空间名为Qspace
namespace Qspace
{
//定义变量
int a = 10;
//定义函数
int Add(int x, int y)
{
return x + y;
}
//定义类型
struct ListNode
{
int val;
struct ListNode* next;
};
}
?命名空间嵌套定义
namespace Addspace
{
int Add(int x, int y)
{
return x + y;
}
//嵌套定义命名空间Subspace
namespace Subspace
{
int sub(int x, int y)
{
return x - y;
}
}
}
?多个文件定义同名的命名空间,编译器最终会合成同一个命名空间中
//test.h文件
# include <iostream>
namespace MSpace
{
int Mul(int x, int y)
{
return x*y;
}
}
//test.cpp文件
# include "test.h"
namespace MSpace
{
int Add(int x, int y)
{
return x + y;
}
namespace Subspace
{
int sub(int x, int y)
{
return x - y;
}
}
}
int main()
{
int ret = MSpace::Mul(2, 3);
printf("%d\n", ret);
return 0;
}
命名空间的使用
方式一:加命名空间名称及作用域限定符(::) 即命名空间名 :: 命名空间成员名
namespace Qspace
{
int a = 10;
int Add(int x, int y)
{
return x + y;
}
struct ListNode
{
int val;
struct ListNode* next;
};
}
int main()
{
printf("%d\n", Qspace::a); //变量的访问方式
int ret = Qspace::Add(2, 3);//函数的访问方式
printf("%d\n", ret);
struct Qspace::ListNode node = { 0, NULL };//结构体类型的访问方式
return 0;
}
方式二:使用using将命名空间中某个成员引入 即using 命名空间名 :: 命名空间成员名
namespace MSpace
{
int a = 10;
int b = 20;
}
//using 命名空间名::命名空间成员名
using MSpace::a;
int main()
{
printf("%d ", a);
return 0;
}
方式三 :使用using namespace 命名空间名引入即using namespace 命名空间名
using namespace 命名空间名 将整个命名空间展开,使得特定命名空间所有成员名可见,此时使用命名空间下的变量、函数不需要加作用域限定符,使得隔离失效;
namespace Addspace
{
int a = 10;
int Add(int x, int y)
{
return x + y;
}
}
using namespace Addspace;
int main()
{
scanf("%d", &a);
int ret= Add(2, 3);
printf("%d\n", ret);
return 0;
}
c++输入与输出
- std是C++标准库的命名空间,cout为iostream所定义的标准输出对象(控制台)(终端)cin为iostream所定义的标准输入对象(键盘),因此使用cout , cin必须包含< iostream >头文件及按命名空间使用方法使用std;
- endl(endline)表示换行输出,相当于换行符,包含在<iostream>头文件中;
- << 流插入运算符(与cout配合使用,可以将输出的变量或者字符串流入到cout中,cout负责输出到终端); ?
- >> 流提取运算符(与cin配合使用,将用户输入的值流入到某变量中);
- cin以遇到空格键,tab键或者换行符作为分隔符,停止读取;
# include <iostream>
using namespace std;
int main()
{
cout << "hello world!" << endl;
return 0;
}
运行结果:
//cin遇到空格键 Tab键 Enter键停止读取
# include <iostream>
using namespace std;
int main()
{
char a[10] = { 0 };
cin >> a;
cout << a << endl;
return 0;
}
运行结果:
注:cout与cin可以自动识别变量类型
缺省参数
缺省参数是声明或定义函数时为函数的形式参数指定一个默认值(缺省值);
调用该函数时,如果没有指定实参则采用形参的缺省值,否则使用指定的实参;
# include <iostream>
using namespace std;
void Fun(int a = 10)
{
cout << a << endl;
}
int main()
{
Fun(); //没有传参时,使用形式参数的默认值
Fun(20); //传参时,使用指定的实参
return 0;
}
?运行结果:
缺省参数的分类
全缺省参数:函数定义或声明时,为该函数所有形式参数指定缺省值;
半缺省参数:函数定义或声明时,为该函数的部分形式参数指定缺省值,但是缺省值只能从右向左给值,必须连续给值;
# include <iostream>
using namespace std;
void Fun(int a = 10,int b=20,int c=30)//全缺省参数
{
cout << "a="<< a ;
cout << "b="<< b ;
cout << "c="<< c << endl;
}
int main()
{
Fun();
Fun(1);
Fun(1, 2);
Fun(1, 2, 3);
return 0;
}
?运行结果:
# include <iostream>
using namespace std;
void Fun(int a,int b=20,int c=30)//半缺省参数
{
cout << "a="<< a ;
cout << "b="<< b ;
cout << "c="<< c << endl;
}
注:缺省参数不能在函数声明和定义同时出现,若声明与定义皆具有缺省参数,恰巧两个位置提供的值不同,此时编译器无法确定到底该采用那个缺省值,当声明与定义分离时,只能在函数声明处给出缺省参数;
函数重载
函数重载:C++允许在同一作用域中声明几个功能类似的同名函数,但是要求同名函数的
形参列表(参数个数 或 参数类型 或 类型顺序)不同,返回值无要求;
//参数类型不同
int Add(int a, int b)
{
return a+b;
}
double Add(double a, double b)
{
return a+b;
}
//参数个数不同
int Fun(int a);
int Fun(int a,int b);
//形参类型的顺序不同
int Sub(int a, char b);
int Sub(char b, int a);
为什么C++支持函数重载,而C语言不支持函数重载呢?
程序运行时,需要经历如下阶段:预处理 编译 汇编 链接,而在链接阶段会生成符号表,建立函数名与地址一一映射的关系,C语言链接函数地址时,采用函数名寻找函数定义,而对于同名函数无法区分函数地址,但是C++是通过函数修饰规则(如Linux下g++ 修饰规则:
_Z + 函数名字符个数+函数名+参数首字母)来区分,修饰后名字不同,自然支持了重载;
引用
引用不是新定义一个变量,而是 给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量 共用同一块内存空间;语法:?? 类型&? 引用变量名(对象名) = 引用实体;
//原变量与引用变量共用同一块内存空间
int main()
{
int a = 10;
int& pa = a;
printf("%p\n", &a);
printf("%p\n", &pa);
return 0;
}
运行结果:
注:引用类型与引用实体必须是相同类型;
引用的特性
引用在定义时必须初始化;
int main()
{
//正确示例
int a = 10;
int& pa = a;
//错误示例
int b = 20;
int& pc;//编译时出错
return 0;
}
一个变量既可以有多个引用又可以给引用变量继续取引用;
int main()
{
int a = 10;
int& pa = a;
int& pb = a;
int& pc = pa;
cout << "a=" << a << endl;
cout << "pa=" << pa << endl;
cout << "pb=" << pb << endl;
cout << "pc=" << pc << endl;
return 0;
}
运行结果:
引用一旦引用一个实体,不能引用其他实体;
int main()
{
int a = 10;
int& pa = a;
int d = 1;
// pa变成d的别名?还是d赋值给pa?
pa = d;//pa的引用实体为a,让引用变量pa引用d
cout << a << endl;
return 0;
}
运行结果:
将d的值赋值给pa,又因为pa是a的引用,所以a的值间接变成了1;
常引用
int main()
{
const int a = 10;
int& b = a;//错误做法
const int& b = a;//正确做法
return 0;
}
变量a由于const修饰具有常属性,不可被修改;而引用变量b与原变量应具有相同属性,不能被修改,但是引用变量放大了权限,导致编译错误;
int main()
{
//权限可以缩小
int c = 20;
const int& d = c;
const int& e = 10;
return 0;
}
变量c的属性为可读写,引用变量d的属性为可读,权限缩小并不会导致编译错误;
int main()
{
int i = 10;
double j = i;//整型提升
double& rj = i;//错误做法
const double& rm = i;//正确做法
return 0;
}
当发生整型提升时,系统不是直接将其赋值给另外一个变量的,而是会创建一个常量区来存放变量提升后的结果,此时变量具有了常属性,一旦出现权限的放大,必然导致编译错误;
总结:类型转换(整型提升 截断)产生临时变量,临时变量具有常属性;
引用的使用场景
引用做参数
C语言阶段函数通过传址调用,实现通过形参修改实参,由于形参与实参数值上相同,空间上独立,所以实参是形参的临时拷贝;而引用变量在语法上与原变量共用同一块内存空间,若形参采用引用变量的方式,也可达到修改实参;
//指针方式
void Swap1(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//引用方式
void Swap2(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
引用做返回值
void Func()
{
int c = 0;
cout << &c << endl;
}
int main()
{
Func();//Func()函数第一次调用结束,销毁第一次为func()函数所开辟的函数栈帧
Func();
return 0;
}
运行结果:
?由于空间可以重复利用,第一次调用Func()函数并为其开辟函数栈帧,并在函数栈帧中为变量c分配空间,当函数运行结束后,该函数所对应的栈空间由操作系统回收,但数据是否被清理是不确定的,当第二次调用Func()函数时,仍然在该地址处创建了c这个变量;
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1,2)=" << ret << endl;
return 0;
}
运行结果:
?ret其实指向的是c那块空间的地址,当c发生了变化,ret也就会随之发生改变;
int& Add(int a, int b)
{
static int c = a + b;
return c;
}
int main()
{
int& ret1 = Add(1, 2);
cout << "Add(1,2)=" << ret1 << endl;
int& ret2 = Add(3, 4);
cout << "Add(3,4)=" << ret2 << endl;
return 0;
}
运行结果:
当Add()函数运行结束后,由于static修饰的局部变量存放于静态区,出函数作用域并不会被销毁,此时可以引用返回,但是第二次调用Add()函数发生错误,原因为静态成员变量只会被初始化一次;
总结:引用做返回值时,返回的数据必须由static修饰或者是存放于堆区的数据或者是全局变量等不会随着函数调用的结束而被销毁的数据;
引用与指针的区别
1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
2. 引用在定义时必须初始化,指针没有要求;
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;
4. 没有NULL引用,但有NULL指针;
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节);
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
7. 有多级指针,但是没有多级引用;
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理;
9. 引用比指针使用起来相对更安全;
内联函数
宏是一种在程序中定义的简单代码替换机制,它们通常用于定义常量或执行重复性操作,与函数不同,宏是在编译时展开的,而不是在运行时调用;
宏的声明方式:
#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
//宏函数
# define Add(x,y) ((x)+(y))
int main()
{
int a = 10;
int b = 20;
int ret = Add(10, 20);
cout << "ret=" << ret << endl;
return 0;
}
宏的缺点:
1、容易出错,语法细节多;
2、不能调试(预编译阶段进行了替换);
3、没有类型安全的检查;
由于宏的缺点从而产生内联函数替代宏函数;
内联函数:以关键字inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率;
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(10, 20);
cout << "ret=" << ret << endl;
return 0;
}
内联函数的特性
- ?inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率;
- inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性;
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到,由于内联函数所生成的地址不会进入符号表,也就没有函数名与地址一一映射的关系,所以发生链接性错误;
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!