C语言和C++的关系:
C++完全兼容C语言,并有自己的特性;
C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛。C++支持多种编程 范式 ——面向对象编程、泛型编程和过程化编程。 其编程领域众广,常用于系统开发,引擎开发等应用领域,是最受广大程序员受用的最强大编程语言之一,支持类:类、封装、重载等特性!
C++在C的基础上增添类,C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。
1. C语言与C++的区别:
1.1 C语言是面向过程编程,而C++是面向对象编程;
1.1.1面向对象:面向对象是一种对现实世界的理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。
1.2 C和C++动态管理内存的方法不一样,C是使用malloc、free函数,而C++不仅有malloc/free,还有new/delete关键字。
1.2.1 malloc/free和new/delete差别:
①、malloc/free是C和C++语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
②、由于malloc/free是库函数不是运算符,不在编译器范围之内,不能够把执行构造函数和析构函数的任务强加入malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,一个能完成清理与释放内存工作的运算符delete。
③、new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。
④、malloc是从堆上开辟空间,而new是从自由存储区开辟(自由存储区是从C++抽象出来的概念,不仅可以是堆,还可以是静态存储区)。
⑤、malloc对开辟的空间大小有严格指定,而new只需要对象名。
⑥、malloc开辟的内存如果太小,想要换一块大一点的,可以调用relloc实现,但是new没有直观的方法来改变。
1.3 C++的类是C中没有的,C中的struct可以在C++中等同类来使用,struct和类的差别是,struct的成员默认访问修饰符是public,而类默认是private。
1.4 C++支持重载,而C不支持重载,C++支持重载在于C++名字的修饰符与C不同,例如在C++中函数 int f(int) 经过名字修饰之后变为_f_int,而C是_f,所以C++才会支持不同的参数调用不同的函数。
-
5 C++中有引用,而C没有
1.5.1 指针和引用的区别:
①、指针有自己的一块空间,而引用只是一个别名。
②、使用sizeof查看一个指针大小为4(32位),而引用的大小是被引用对象的大小。
③、指针可以是NULL,而引用必须被初始化且必须是对一个以初始化对象的引用。
④、作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象。
⑤、指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被修改。
⑥、指针可以有多级指针(**p),而引用只有一级。
⑦、指针和引用使用++运算符的意义不一样。
1.6 C++全部变量的默认连接属性是外连接,而C是内连接。
1.7C中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以。
1.8C++有很多特有的输入输出流。
2. 面向过程与面向对象的区别:
3. C++新语法(存储结构):
3.1引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
3.1.1 引用的声明方法:类型标识符 &引用名=目标变量名;
如下:定义引用ra,它是变量a的引用,即别名。
int a;
int &ra=a;
(1)&在此不是求地址运算符,而是起标识作用。
(2)类型标识符是指目标变量的类型。
(3)声明引用时,必须同时对其进行初始化。
(4)引用声明完毕后,相当于目标变量有两个名称即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。
(6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
特点:变量的别名,不占空间
#include<iostream.h>
void main(){
int a=5;
int &b=a;
b=6;
cout<<"a="<<a<<",b="<<b<<endl;//a=6,b=6
int c=7;
b=c;
cout<<"a="<<a<<",b="<<b<<endl;//a=7,b=7
}
#include<iostream.h>
void main(){
int a[]={1,2,3,4};
int &b=a;
//编译错误:cannot convert from 'int [4]' to 'int &'
}
引用的应用
- 引用作为参数
引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。
#include<iostream.h>
////此处函数的形参p1, p2都是引用
void swap(int &p1,int &p2){
int p=p1;
p1=p2;
p2=p;
} 为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:
void main(){
int a,b;
cin>>a>>b;//输入a,b两个变量的值
swap(a,b);//直接以a和b作为实参调用swap函数
cout<<"a="<<a<<",b="<<b<<endl;
}
上述程序运行时,如果输入数据10 20并回车后,则输出结果为a=20,b=10。
由上例可以看出:
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
- 常引用
常引用声明方式:const 类型标识符 &引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
#include<iostream.h>
void main(){
int a=1;
int &b=a;
b=2;
cout<<"a="<<a<<endl;//2
int c=1;
const int &d=c;
// d=2;//编译错误 error C2166: l-value specifies const object
c=2;//正确
}
这不光是让代码更健壮,也有其它方面的需求。
【例4】:假设有如下函数声明:
string foo();
void bar(string &s);
那么下面的表达式将是非法的:
bar(foo());
bar("hello world");
原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。
- 引用作为返回值
要以引用返回函数值,则函数定义时要按以下格式:
类型标识符 &函数名 (形参列表及类型说明)
{ 函数体 }
说明:
(1)以引用返回函数值,定义函数时需要在函数名前加&
(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
【例5】以下程序中定义了一个普通的函数fn1(它用返回值的方法返回函数值),另外一个函数fn2,它以引用的方法返回函数值。
#include<iostream.h>
float temp;//定义全局变量temp
float fn1(float r);//声明函数fn1
float &fn2(float r);//声明函数fn2 r
float fn1(float r){//定义函数fn1,它以返回值的方法返回函数值
temp=(float)(r*r*3.14);
return temp;
}
float &fn2(float r){//定义函数fn2,它以引用方式返回函数值
temp=(float)(r*r*3.14);
return temp;
}
void main(){
float a=fn1(10.0);//第1种情况,系统生成要返回值的副本(即临时变量)
// float &b=fn1(10.0); //第2种情况,可能会出错(不同 C++系统有不同规定)
/*
编译错误:cannot convert from 'float' to 'float &'
A reference that is not to 'const' cannot be bound to a non-lvalue
*/
//不能从被调函数中返回一个临时变量或局部变量的引用
float c=fn2(10.0);//第3种情况,系统不生成返回值的副本
//可以从被调函数中返回一个全局变量的引用
float &d=fn2(10.0); //第4种情况,系统不生成返回值的副本
cout<<"a="<<a<<",c="<<c<<",d="<<d<<endl;
//a=314,c=314,d=314
}
引用作为返回值,必须遵守以下规则:
(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。如【例5】中的第2种情况出现编译错误。
(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)引用与一些操作符的重载:流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
【例6】 测试用返回引用的函数值作为赋值表达式的左值。
#include<iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main(){
put(0)=10;//以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20;//以put(9)函数值作为左值,等价于vals[9]=20;
cout<<vals[0]<<endl;//10
cout<<vals[9]<<endl;//20
}
int &put(int n){
if(n>=0 && n<=9)
return vals[n];
else{
cout<<"subscript error";
return error;
}
}
- 引用和多态
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
【例7】:
class A;
class B:public A{ ... ... }
B b;
A &Ref = b;//用派生类对象初始化基类对象的引用
Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
引用总结
(1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。
(2)用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。
(3)引用与指针的区别是,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
(4)使用引用的时机。流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
引用类型:(不占内存)
-
概念:不占用内存空间,用于充当变量的别名
-
语法格式: 数据类型& 变量名 = 变量 :(必须初始化)
-
特点:
-
-
引用必须初始化
-
引用只能初始化一次,后续不能再充当别的变量的引用
-
不存在多级引用
-
不存在引用数组
-
引用的数据类型和变量的数据类型必须完全一致;
-
不能有void类型的引用
-
-
#include <iostream>
using namespace std;
int main()
{
int a = 100;
int b = -233;
int& pa = a;
cout << pa << endl;
pa = 12;
cout << pa << endl;
//取别名只能用一次,pa = a,当pa = b时,就是a = b;修改的是a的值
pa = b;
cout << a << endl;//a = -299
cout << b << endl;//b = -299
cout << pa << endl;//pa = -299
//给pa起别名 == 给a取别名
int& paa = pa;
paa = 101;
cout << a << endl;//a = 101
cout << pa << endl;//pa = 101
cout << paa << endl;//pa = 101
return 0;
}
给指针起别名(引用)
int main()
{
int a = 100;
int* p = &a;//指针
int*(&pp) = p;//引用
int** ppp = &p;//指针
int** (&pap) = ppp;//引用
cout << "p = " << *pp << endl;
cout << "sizep = " << sizeof(p) << endl;
cout << "sizepp = " << sizeof(pp) << endl;
cout << "sizea = " << sizeof(a) << endl;
cout << "sizeppp = " << sizeof(ppp) << endl;
}
int main()
{
int a = 100;
int b = -233;
int* p = &a;//指针
int* & pa = p;
cout << pa << endl;
return 0;
}
给数组起别名(引用)
int main()
{
int a[3] = { 1,2,3 };
int(&pa)[3] = a;
cout << pa[1] << endl;
cout << sizeof(pa) << endl;
return 0;
}
引用数组->不存在,因为引用不占内存;
函数的引用:
int main()
{
void(*pFun)(int) = Fun; //函数指针
pFun(100);
//函数的引用
void(&fFun)(int) = Fun;
fFun(98);
return 0;
}
引用的数据类型和变量的数据类型必须完全一致;
int main()
{
unsigned short a = 100;
unsigned short& pa = a;
}
引用不能有void类型,但有void*类型指针;
引用充当函数参数及返回值问题
- 引用充当函数实参(没区别)
#include<iostream>
using namespace std;
#if 0
void Fun(int x, int y)
{
++x;
++y;
cout << "Fun:" << x << "," << y << endl;
}
int main()
{
int a, b;
a = 88;
b = 78;
int& pa = a;
int& pb = b;
Fun(pa, pb);//引用充当函数的实参,实际上存储的事变量本身
cout << a << "," << b << endl;
return 0;
}
#endif
引用充当函数的形参:实际上间接修改了实际参数
#include<iostream>
using namespace std;
#if 0
void Fun(int& x, int& y)
{
++x;
++y;
cout << "Fun:" << x << "," << y << endl;
}
int main()
{
int a, b;
a = 88;
b = 78;
//int& pa = a;
//int& pb = b;
Fun(a, b);//引用充当函数的实参,实际上存储的事变量本身
cout << a << "," << b << endl;
return 0;
}
#endif
函数的返回值是引用,则意味着返回了变量的空间本身,因此函数此时可以充当左值;
#include <iostream>
using namespace std;
#if 0
//函数的返回值是引用,则意味着返回了变量的空间本身,因此函数此时可以充当左值
int& Fun()
{
static int x = 899;
cout << x << endl;
return x;
}
int main()
{
//int res;
//res = Fun();
//cout << res << endl;
Fun() = 900;
Fun();
return 0;
}
#endif
4. 右值引用(C++11特性):
C++11 引入了右值引用(Rvalue references),这是一种特殊的引用类型,它可以用来引用即将被销毁的对象,也就是右值(rvalues)。右值引用的引入主要是为了支持移动语义(move semantics)和完美转发(perfect forwarding)。
右值引用的语法
右值引用使用双&&
来声明,例如:
int&& rvalueRef = 10; // 10 是一个右值
右值引用的作用
-
移动语义(Move Semantics):允许资源的“移动”,而不是“复制”。这可以减少不必要的资源复制,提高效率。
例如,如果你有一个资源密集型对象,如动态分配的内存,移动语义允许你将资源从一个对象转移到另一个对象,而不是复制资源。
std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = std::move(v1); // 移动v1的资源给v2,v1变为空
-
完美转发(Perfect Forwarding):允许函数模板完美地转发参数给另一个函数,保持参数的左值/右值特性。
例如,模板函数可以接收任意类型的参数,并将其转发给另一个函数,同时保持参数的左值或右值特性。
template<typename T> void wrapper(T&& arg) { someFunction(std::forward<T>(arg)); }
右值引用与左值引用的区别
- 左值引用(Lvalue references):可以绑定到左值(lvalues)和右值(rvalues),但绑定到右值时,右值会被转化为左值。
- 右值引用:只能绑定到右值,不能绑定到左值。
右值引用的规则
- 左值可以绑定到左值引用,但右值引用只能绑定到右值。
- 右值引用可以绑定到左值,但这种情况下左值会被“移动”。
- 右值引用可以绑定到右值引用,但这种情况下会发生引用折叠(reference collapsing),结果仍然是一个右值引用。
总结
右值引用是C++11中一个强大的特性,它使得资源管理更加高效,同时也支持了模板编程中的完美转发。通过使用右值引用,开发者可以编写出更加高效和灵活的代码。
#include <iostream>
using namespace std;
double add(double x, double y)
{
return x + y;
}
int main()
{
double x = 1.1, y = 1.3;
10;
x + y;
add(1, 2);
//右值引用
int&& r1 = 10;
cout << "r1 = " << r1 << endl;
double&& r2 = x + y;
cout << "r2 =" << r2 << endl;
r2 = 1;
cout <<"r2 =" << r2 << endl;
double&& r3 = add(x, y);
cout <<"r3 = " << r3 << endl;
int&& r4 = add(1, 2);
cout <<"r4 = " << r4 << endl;
int m = r4;
cout << "m = " << m << endl;
return 0;
}
5. 引用与指针的区别:
C++中的引用和指针是两种不同的机制,它们在内存管理和使用上有着本质的区别。以下是引用和指针的主要区别:
-
定义和初始化:
- 引用:引用必须在定义时被初始化,一旦初始化后,它就不能再指向另一个对象。引用的声明和初始化是同时进行的。
int a = 10; int& ref = a; // 引用必须在声明时初始化
- 指针:指针可以在定义时不初始化,可以在任何时候指向任何对象。
int* ptr; // 指针可以在声明时不初始化 int b = 20; ptr = &b; // 指针可以在任何时候指向另一个对象
- 引用:引用必须在定义时被初始化,一旦初始化后,它就不能再指向另一个对象。引用的声明和初始化是同时进行的。
-
内存占用:
- 引用:引用本身不占用内存,它只是目标对象的一个别名。
- 指针:指针本身需要占用内存来存储它所指向的对象的地址。
-
类型转换:
- 引用:引用一旦被初始化,其类型就固定了,不能改变。
- 指针:指针可以很容易地进行类型转换,例如,可以将一个
int*
转换为void*
。
-
多重间接:
- 引用:引用不能被多重间接,即不存在引用的引用(
int&&
是右值引用,不是引用的引用)。 - 指针:指针可以被多重间接,例如
int** ptr
。
- 引用:引用不能被多重间接,即不存在引用的引用(
-
运算符:
- 引用:对引用的操作和对原始对象的操作完全一样,不需要使用解引用运算符
*
。int& ref = a; ref = 30; // 直接赋值,不需要解引用
- 指针:对指针的操作需要使用解引用运算符
*
。int* ptr = &a; *ptr = 30; // 需要解引用来赋值
- 引用:对引用的操作和对原始对象的操作完全一样,不需要使用解引用运算符
-
数组和函数参数:
- 引用:引用可以作为函数参数,以避免复制大型对象,并且可以返回函数内部的局部变量的引用(尽管这通常不是一个好习惯)。
- 指针:指针也可以作为函数参数,但它们通常用于处理动态分配的内存或数组。
-
安全性:
- 引用:引用更安全,因为它们不能被重新赋值为另一个对象,也不能被设置为
nullptr
。 - 指针:指针可以被设置为
nullptr
,也可以指向任何对象,包括无效的内存地址,这可能导致程序崩溃。
- 引用:引用更安全,因为它们不能被重新赋值为另一个对象,也不能被设置为
-
nullptr和空引用:
- 引用:引用不能被设置为
nullptr
,也不能指向空。 - 指针:指针可以被设置为
nullptr
,表示它不指向任何对象。
总的来说,引用提供了一种安全、简洁的方式来访问对象,而指针则提供了更多的灵活性和控制,但同时也带来了更多的复杂性和潜在的错误。在现代C++编程中,引用通常被优先使用,特别是在函数参数和返回值中,以避免不必要的复制和提高代码的可读性。指针仍然在处理动态内存分配、数组和某些底层操作中发挥着重要作用。
- 引用:引用不能被设置为
6. 引用数组:
不存在引用数组
7. bool类型:
1、C++中的布尔类型
(1)C++在C语言的基础类型系统之上增加了bool;
1)C语言中,没有bool类型存在,往往都是用整型代替bool类型,常用0表示假,1表示真;
2)bool本来就有这样的类型,但是在C语言中却没有这样的基本类型,所以只有使用整型代替bool类型,但是不严谨。
3)这也是C++中的“+”的体现;
(2)C++中的bool可能的值只有true和false;
1)true代表真值,编译器内部用1来表示(但是会将非0值转换为1存储);
2)false代表非真值,编译器内部用0来表示;
(3)理论上bool之占用1个字节
布尔类型是C++中的基本数据类型
1)可以定义bool类型的全局变量;
2)可以定义bool类型的常量;
3)可以定义bool类型的指针;
4)可以定义bool类型的数组;
5)...;
bool类型:用于存储真假值;
-
取值:true (1) false(0);
-
该变量的大小占1byte空间
#include <iostream>
using namespace std;
int main()
{
bool ok;
cout << sizeof(ok) << endl;
ok = 0;
cout << ok << endl;
return 0;
}