一、C/C++的区别
1、与C相比
c语言面向过程,c++面向对象。
c++能够对函数进行重载,可使同名的函数功能变得更加强大。
c++引入了名字空间,可以使定义的变量名更多。
c++可以使用引用传参,引用传参比起指针传参更加快,指针传参还需要传送,而引用传参不需要传送参数,就能使函数共用一个变量。
c++使用了类,能够继承,继承使得定义相似的类时能够直接从上一层得到一些方法或变量来进行直接使用。
c++相比C语言功能强大的同时,也带来了更复杂多样的语法,这对于初学者来说是一个大的难点,这也使得c++在编程和学习上的难度提高了,并且c++的效率仍然不如C语言,c语言适合底层开发,c++适合c/s的软件开发. c/s — client /server。
2、C的扩展
C++是在C语言的基础上扩展而来,所以C++ 完全兼容C语言,可以在C++程序中写C语言的代码。
二、在c++中使用c语言库函数
extern "C"{//标准C库函数的头文件#include<stdio.h>//自定义的第三方C库头文件//也就是你使用的C编译器编译好的库对应的头文件#include "add.h"
}
例子:编译一个动态库 libadd.so
gcc -fpic -shared add.c -o libadd.so
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的方式进行编译,而不是C++的。
c++标准的头文件不带.h
三、命名/名字/名称空间 namespace
1、问题的引入
探讨:在实际的系统开发中,所有的开发工作,通常都不会仅由一个人来开发完成,不同的人只负责自己功能模块,他们在设计时,比如声明变量,通常情况下,不会考虑其他人是否已使用该变量名,在模块功能测试时,相互之间是独立的,完全没有问题,当系统功能合成 时,不可避免地会出现变量或函数的命名冲突,因为变量或函数重名而导致的问题将会造成一定的混乱。所以如何在同一个.cpp文件中定义相同变量名的两个全局变量呢
#include<iostream>int val = 200;
int val = 300;int main()
{return 0;
}
现象:编译会报错
解决方法:使用命名空间区分这两个全局变量
2、概念
其实命名空间就是类似一个黑盒子
3、作用
防止名字冲突
4、格式
namespace 命名空间名字
{//声明定义变量//声明定义函数//定义结构体/共用体/枚举 数据类型//声明类 定义对象
}
5、使用命名空间中的成员
1)using编译指令( 引进整个命名空间) —将这个盒子全部打开
using namespace 命名空间名字;
2)using声明使特定的标识符可用(引进命名空间的某个成员) —将这个盒子中某个成员的位置打开
using 命名空间名字::成员名;
3)调用的时候指定是哪一个命名空间下的成员
cout<<命名空间名字::成员名
6、实例
#include<iostream>using namespace std;//使用名称空间LcdSpace -501老王
namespace LcdSpace{int x = 10;int y = 20;typedef struct{int data;}Data_t;enum {LCD_COLOR_RED,LCD_COLOR_GREEN};int lcd_init(){return 0;}};
//使用名称空间TouchSpace -502老王
namespace TouchSpace{int x = 100;int y = 200;
};//外部的全局变量 --老外
//int x = 1000;
//int y = 2000;//2)使用using声明的方式去引入名字空间中的某个成员
//把这个TouchSpace黑盒子 打开一个角落,正好把某个成员放出来
//格式:using 命名空间名字::成员名;
//using TouchSpace::x;void test01()
{using namespace LcdSpace;cout<<"x:"<<x<<" y:"<<y<<endl;
}int main()
{//3)使用using编译指令的方式 打开整个名字空间 using namespace TouchSpace;//如何使用名字空间里面的成员//1)使用 作用域限定符:: 去指定//格式: 名字空间的名字::成员变量名std::cout<<TouchSpace::x<<" "<<TouchSpace::y<<std::endl;std::cout<<x<<" "<<y<<std::endl;std::cout<<LcdSpace::x<<" "<<LcdSpace::y<<std::endl;LcdSpace::Data_t data;LcdSpace::lcd_init();//我们有时候为了强调该变量的全局特性,可以在前面加上::修饰cout<<x<<" "<<y<<endl;test01();return 0;
}
7、命名空间的嵌套
namespace myspace{int x=10;int y=20;namespace spaceA{int a=100;void setValue(int data){a = data;}void printValue(){std::cout<<"a:"<<a<<std::endl;}}
}
使用:
myspace::spaceA::printValue();
8、同名命名空间
命名空间可以分开定义,也就是说,你在定义一个命名空间之后,如果后面想要在这个命名空间里面再次添加某些数据成员或者函数成员,可以再次接着定义。
namespace myspace{int x=10;int y=20;
}
namespace myspace{int a=100;int b=200;void print(){std::cout<<"x:"<<x<<" y:"<<y<<std::endl;}
}
9、匿名命名空间
namespace {int data=123;
}
可以在本文件中直接使用data, 限制空间中的成员只能在本文件中使用,类似c语言中的static修饰
10、全局作用域
全局作用域是从C语言就开始有的一种作用域,在C++中,有时为了强调某符号的全局特性,或为了避免与导入的名字空间中的重名符号冲突,会在使用全局符号的时候加上 作用域解析符
int global = 100;
int main()
{int global = 200;// 重名的标识符,外层的作用域会被内层的掩盖cout << global << endl; // 输出200// 使用双冒号引用全局作用域中的标识符cout << ::global << endl; // 输出100
}
1.全局作用域的名字空间是匿名的,引用全局作用域符号只需加 :: 即可。
2.名字空间的本质就是作用域,遵守C语言关于作用域的基本原则,如内层作用域重名符号会掩盖外层作用域的重名符号。
四、引用
1、问题的引入
//值传递
int swap1(int a,int b)
{a = a+b;b = a-b;a = a-b;
}
//地址传递
int swap2(int *pa,int *pb)
{int temp = *pa;*pa = *pb;*pb = temp;
}
int main()
{int aa = 100;int bb = 200;swap1(aa,bb);//值传递swap2(&aa,&bb);//地址传递
}
问题:值传递书写方式简单,但是不能修改变量所在内存空间的数据;地址传递能够修改变量所在内存空间的数据,但是需要复杂的解引用写法,而且使用过程中可能出现问题(空指针,不合法内存),导致程序不安全。
有没有一种方法:集中两者的优点呢,也就是说,既能够书写简单,同时也能修改内存空间的数据,提高程序的安全性!
解决方法:使用C++中的引用。
2、概念
引用就是一个变量或者是常量的别名,对引用的操作与对变量直接操作完全一样。引用是一种关系型声明的类型,说明它跟别的变量的关系,它所声明的变量不占内存空间,通俗来讲,是已有变量的别名,来说明跟已有变量的关系,所以引用变量的类型要跟已有变量的类型保持一致。
3.定义格式
以前的指针:数据类型 * 指针名 = &变量
现在的引用:数据类型 & 引用名 = 变量(数据类型必须与变量类型一致,而且必须要初始化)
int a = 100;
//给变量a起一个别名(定义引用)
//int 表示引用变量的数据类型,ra表示给变量a起的小名字就叫做ra
int &ra = a;
char *str = "hello";
char*(&rs) = str;//给指针变量str定义一个引用rs//给常量10起一个别名,给常量起一个别名必须在定义引用的时候加const 修饰
const int &rval = 10;
5.引用特点
- &在定义的时候不是取地址运算符,而是起标识作用,表示给某个变量或者常量起一个别名,所以引用不是变量。
- 在定义引用的时候,数据类型指的是 目标变量或者常量的数据类型。
- 定义引用的时候,必须同时对其进行初始化。
- 引用定义完毕后,相当于目标变量有两个名称,也就是目标原变量名和引用名,直接使用引用名就相当于直接操作该目标变量对应的内存空间。
- 引用定义完毕后,不能 再把该引用名作为其他变量名的别名
- 引用本身不占存储单元,仅仅是作为变量的别名。对引用求地址,就是对目标变量求地址。&ra与&a相等。
- 不能建立引用的数组。因为数组是一个由若干个元素所组成的集合,所以无法建立一个由引用组成的集合,但是可以建立数组的引用。
const int &arr[3]={1,2,3}; 引用的数组----错误 [] 优先级 大于 &
int n[3] = {2, 4, 6};
int (&rn)[3] = n; //数组的引用
- 如果引用的对象是常量,那么引用必须用const修饰
int a = 100;
int b = 200;
//给变量a起一个别名(定义引用)
int &ra = a;
ra = b;//将变量b的值赋值给变量a所在的内存空间
cout<<"a:"<<a<<endl;
//引用取地址与变量取地址相等
cout<<"&a:"<<&a<<" &ra:"<<&ra<<endl;
//给常量10起一个别名
const int &rval = 10;
6.引用与常量
- 将一个引用设置为常量后,不能通过该引用修改数据,但仍可通过被引用的变量来改变
int a = 100;
const int & b = a;//b和a是同一内存,但不能直接修改b
b = 1000;//错误
a = 1000;//正确
被引用的数据是常量,引用本身也必须是常量:
const int a = 100;
const int &b = a;//OK
int &b = a;//error
int &ra = 10;//error
const &ra = 10;//OK
7.引用的应用
- 引用作为参数(值, 地址, 引用)
引用的一个重要作用就是作为函数的参数。C语言阶段 如果在某个函数中想要改变另一个函数的变量,则需要进行地址传递,现在C++阶段又新增了一个引用传递,同样也能在一个函数中修改另一个函数的变量。
int func(int &ra,int &rb)
{ra = ra + rb;rb = ra - rb;ra = ra - rb;
}
//如果仅仅是访问变量的数据而不做修改,可以设置为常引用,达到引用的安全性
//常引用不能通过引用修改目标变量的值,
int print(const int &ra,const int &rb)
{cout<<ra<<" "<<rb<<endl;
}
int main()
{int a = 10;int b = 20;func(a,b); // func(10,20); 错误 ,因为该函数定义的参数不是常引用print(a,b);
}
- 引用作为函数返回值
格式:
返回值数据类型 &函数名(形参列表及类型说明)
{
}
特点:
a. 以引用的形式返回函数值,定义函数时需要在函数名前加&
b. 用引用返回一个函数值的最大好处是,在内存中不产生被返回值 的副本。
#include <iostream>
using namespace std;int g_a; // 定义全局变量g_aint& test() {g_a = 168;return g_a;
}int main()
{//左值变量i 是直接从变量g_a中拷贝int i = test();cout << i << endl; return 0;
}
c. 不能返回局部变量的引用 ,因为局部变量在函数调用结束之后内存空间就被释放了
char*& initMem(int size)
{char *p = new char[size];return p;
}
int main()
{//会发生段错误,不能返回局部变量的引用char*(&p) = initMem(1024);strcpy(p,"hello");
}
正确写法:把initMem函数的p指针改为全局变量或者静态变量
char*& initMem(int size)
{static char *p = new char[size];return p;
}
d. 如果一个函数的返回值是一个引用,那么函数可以调用可以作为=的左值
test() = 100;
int val=100;int& getData()
{return val;
}
int main()
{getData() = 200;cout<<"val:"<<val<<endl;}
8.总结
引用传递的性质像指针传递,书写形式像值传递
如果只需要借用一下别名,就没必要要用指针
练习1:求下面的结果
#include <iostream>
using namespace std;
int f(int i){ return ++i; }
int& g(int &i){ return ++i; }
int h(int i){ return ++i; }
int main()
{ int a = 0, b = 0, c = 0; a += f(g(a)); b += g(g(b)); c += f(h(c)); cout << "a=" << a << ",b=" << b << ",c=" << c << endl; return 0;
}