- 1.C++的起源
- 2.C++关键字
- 3.C++ 命名空间
- 3.1 命名空间的定义
- 3.2 命名空间的使用
- 4.C++ 输入和输出
- 5.缺省参数
- 5.1 了解缺省参数
- 5.2 缺省参数分类
- 6.函数重载
- 6.1 函数重载概念
- 6.2 函数重载原理(选读)
- 7. 引用(左值引用)
- 7.1 引用概念
- 7.2 引用使用场景
- 1.做参数
- 2.做返回值
- 传引用的函数重载
- 7.3常引用
- 7.4 指针和引用的区别
1.C++的起源
对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。
1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
2.C++关键字
c++有63个关键字,比起C语言的32个关键字,约多出一倍。
3.C++ 命名空间
函数和部分变量存在于全局作用域中,应避免函数名以及变量名在日后项目逐渐的扩大的过程中造成的重名冲突。
使用命名空间的目的就是对标识符的名称进行本地化,在自己命名的空间中声明,定义函数和变量以防止重名。
a.普通使用
namespace Mine //关键字——namespace 空间名——Mine
{
//定义变量
int time;
//定义函数
void Func()
{
printf("namespace Minen");
}
//定义结构体类型
struct Node
{
struct Node* next;
int data;
};
}
b.命名空间可以嵌套
namespace Mine
{
//定义变量
int time = 0;
//定义函数
void Func()
{
printf("namespace Minen");
}
//定义类型
struct Node
{
struct Node* next;
int data;
};
namespace Mine2
{
void Func()//注意这里函数的名字是Mine2专属的,即使嵌套在Mine的空间中,也不会造成冲突
{
printf("namespace Mine2n");
}
}
}
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
c.命名空间的名字冲突了怎么办?
多个相同名称的命名空间会在编译时合并成一个命名空间,但是同命名空间下的同名变量或是函数名仍会造成冲突!
//在source.h文件中
namespace myspace
{
int rand;
double Add(double a, double b);
int Mul(int a, int b);
}
//在source.c文件中
namespace myspace
{
//int rand;//这里会发生冲突,应避免
double Add(double a, double b)//可以函数重载
{
return a + b;
}
int Mul(int a, int b)
{
return a * b;
}
}
同名namespace,多用于头文件中的声明,和源文件中的定义。
- 方式一:添加命名空间名称和作用域限定符(::)
int main()
{
Mine::time = 10;
printf("%dn", Mine::time);
Mine::Func();
Mine::Mine2::Func();
return 0;
}
- 方式二——使用using 释放一部分命名空间中成员可直接使用,但与此同时被释放的成员失去了隔离,将会有可能与全局作用域的某些量发生冲突!
using Mine::Mine2::Func;//释放了Mine2中的Func,现可以直接使用
int main()
{
Func();
return 0;
}
如果同时释放了Mine的Func
using Mine::Func;
using Mine::Mine2::Func;
int main()
{
Func();
return 0;
}
产生了冲突
关于函数重载,会在下文谈及。
- 方式3 ——使用using namespace +空间名,将命名空间的名称全数引入全局作用域
using namespace Mine;
int main()
{
Func();
Mine2::Func();
return 0;
}
4.C++ 输入和输出
C++的第一声呼唤
#includeusing namespace std; int main() { cout<<"Hello world!"<<" "<<"from CPP"< 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
int main() { int a; double b; cin>>a>>b; cout<
- 符号>>和<<表示将数据输入和输出到流
- endl(end line)表示换行。
- 使用C++输入输出更方便,不需增加数据格式控制,比如:浮点型–%f,字符串–%s。
5.缺省参数 5.1 了解缺省参数在声明或定义函数时,为函数的参数设定默认值,在调用函数时,如果没有指定实参(缺省实参),则采用默认值作为参数。
void Func(int a=0) { cout< 5.2 缺省参数分类
- 全缺省参数
void Func1(int a = 1, int b = 2, int c = 3) { cout <<"a="<< a << " "; cout <<"b="<< b << " "; cout <<"c="<< c << endl<实参从左到右依次给到形参
- 半缺省参数
缺省部分参数 – 必须从右往左缺省,且必须连续缺省void Func2(int a,int b=1,int c=2) { cout <<"a="<< a << " "; cout <<"b="<< b << " "; cout <<"c="<< c << endl<
- 注意缺省参数不能在函数声明和定义中同时出现
//source.h void TestFunc(int* a,int n=10 ); //source.c void TestFunc(int *a,int n=10) {}
如果定义和声明同时出现缺省,恰巧提供的默认值不同,会给编译器造成歧义。
- 缺省值必须是常量或全局变量
6.函数重载 6.1 函数重载概念如果在同一个作用域的几个函数名字相同但参数列表不同(参数个数,顺序,类型不同),我们称之为重载函数。
参考下面的函数声明
void print(const char *cp); void print(const int* beg,const int* end); void print(const int ia[],size_t size);这些函数接受的形参不同,编译器会根据实际接受的实参类型推断想要的是哪一个函数。
int a[2]={0,1}; int b[2]=[8,9]; print("hello world");//调用void print(const char *cp); print(a,2);//调用void print(const int ia[],size_t size); print(a,b);//调用void print(const int* beg,const int* end);
- 注意 main函数不能重载
- 函数重载与缺省参数
void f() { cout<<"f()"<注意上面的两个函数是构成重载的,因为参数不同。但是使用时会造成歧义。
6.2 函数重载原理(选读)
传参时,编译器可以区别出两个函数,但是不传参会报错。
在一个程序运行起来要经过几个阶段:
- 预处理、编译、汇编、链接
我们使用linux的gcc编译器对如下程序进行编译,以编译程序test.c为例:
gcc -E——预处理,生成的文件test.i
gcc -S——编译生成汇编代码,生成的文件为test.S
gcc -c——汇编生成机器码,生成的文件test.o
gcc——执行链接,生成默认名为a.out的可执行文件
图示:
1.我们的多个项目会有多个头文件和源文件组成,在预处理阶段,头文件在包含的源文件中展开,源文件在各自编译时,只能获取到函数的声明【test.o调用了Add函数,但没有Add的函数定义】,而真正的Add函数在sum.o中定义。
2.链接器看到test.o调用了Add,但是Add没有地址,于是就到sum.o的符号表中找到Add的地址,然后链接到一起。
3.那符号表是如何描述函数的呢?
我们在用gcc(C语言编译器)编译时看到的结果如下:
很显然,c语言编译时,其符号表是直接记录函数名的,同函数名即使参数不同编译器也不会去识别,所以函数重载自然不能在C中使用,
我们再使用g++(C++编译器)试一下:
g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】,函数的名字修饰发生改变,函数参数类型信息添加到了修改的名字之后,即使是同名函数,也能通过参数的相异来判断使用的是哪一个,于是我们也就理解了函数重载要求参数不同,而与返回值没有关系!
7. 引用(左值引用) 7.1 引用概念引用为对象起了另外一个名字,编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块内存空间。
类型& 引用变量名=引用实体;
int val=10; int& refval=val;//refval指向val,是val的另一个名字 int& refval2;//错误,引用必须初始化一般在初始化变量时,初始值会被拷贝到变量中,然而定义引用时,程序就把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用,一旦初始化完成,引用就和初始值一直绑定在一起。
为引用赋值,实则把值赋给了与其绑定的对象。
无法将引用重新绑定到另一个对象上,所以引用必须初始化引用并非对象,它只是已经存在的对象的别名
以引用作为初始值,实际就是将绑定的对象作为初始值。
int& refval3=refval;//正确,这里就是绑定在val上 int i=refval;//正确,i被初始化为val的值&就是引用的标识符,在一行里定义多个引用,每个名字都需要以&开头
int i1=10,i2=20; int &r1=i1,&r2=i2;7.2 引用使用场景引用的价值体现在函数调用中的传参和传值返回。
1.做参数
这两者都存在拷贝的情况:引用在做函数形参时与指针价值相同
a.如果实参的数组较大,传值将拷贝全部数组,而传指针和传引用可以节省了大量拷贝的时间:
struct A { int arr[100000]; }; void func1(struct A a)//传值 {} void func2(struct A* a)//传址 {} void func3(struct A& a)//传引用 {} int main() { A a; int begin1 = clock(); for (int i = 0; i < 1000; i++) { func1(a); } int end1 = clock(); int begin2 = clock(); for (int i = 0; i < 1000; i++) { func2(&a); } int end2 = clock(); int begin3 = clock(); for (int i = 0; i < 1000; i++) { func3(a); } int end3 = clock(); printf("传值耗时:%dn", end1-begin1); printf("传指针耗时:%dn", end2 - begin2); printf("传引用耗时:%dn", end3 - begin3); }2.做返回值b.形参的修改可以影响实参(输出型参数)
a.返回局部变量(错误)
由于函数作用域的变量的返回是拷贝,千万不要返回函数作用域中局部变量的引用!因为在函数结束时栈帧销毁,无法保存引用的对象。
此时,虽可以得到函数局部变量的引用,但已引起非法访问,因为函数栈帧已名存实亡了。
在后续使用其他函数时,会对原有栈帧形成覆盖,引用的对象在不被保护的情况下,自然就被“掩埋”。
b.出了作用域还存在的变量可以使用传引用返回,如全局变量,静态变量,外部传进函数的参数变量,malloc的空间对象等。
返回引用的好处在于可读可写,因为一旦返回引用就具有了左值的功能(可被修改):
const int N = 10; int& At(int i) { static int a[N]; return a[i]; } int main() { for (int i = 0; i < N; i++) { At(i) = N + i;//可对返回值赋值 } for (int i = 0; i < N; i++) { cout << At(i) << endl; } }传引用的函数重载
传引用可以重载,但调用时与传值产生歧义,不知道调用时是传值还是传引用。1.引用在传参和传返回值,在有些场景中,可以提高性能(大对象+深拷贝对象)
7.3常引用
2.引用在传参和传返回值,输出型参数和输出型返回值。通俗点说,形参的改变可以改变实参。可以改变返回对象。
- const Type& 引用名
将引用绑定在const对象上,我们称之为对常量的引用。
- 常量引用绑定非常量对象,字面值,以及一般表达式
见如下报错:
当添加const后,报错消失
要想理解这种例外情况的产生,最简单的办法就是弄清楚当一个常量引用被绑定到另一个类型上时,到底发生了什么:
double a=1.1; const int& ra=a;ra为int型引用,但是却绑定在了一个双精度浮点数上,因此为了确保ra绑定的是一个整型,编译器进行了如下修改:
const int temp=a; const int& ra=temp;这里ra绑定了一个带有常性的临时量对象,所谓的临时量对象就是编译器需要一个空间来暂存表达式的求值结果临时创建的未命名变量。
临时变量是右值,其所接受的隐式类型转换,表达式结果,字面值都具有常量属性无法修改,所以需要加const。
- 如果函数不修改参数的值,使用常量引用作为形参,既可以节省拷贝的时间空间,也能够防止函数对形参进行修改
void StackPrint(const struct Stack& st);传来的形参如果是const对象,普通对象,临时对象,const引用作为形参都是通吃的。
7.4 指针和引用的区别
- 引用在定义时必须初始化,指针并不是
- 引用在初始化时绑定一个实体后,就不能再引用其他实体。而指针没有这个限制
- 没有NULL引用,但可以定义NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 运算:引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全



