1 C++对C的加强
1.1 namespace命名空间
1.1.1 C++命名空间基本概念
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字就是针对这种问题而出现的。
1.1.2 C++命名空间定义,使用语法,意义
namespace + 命名空间名称 + { 命名空间成员 }
1.1.3 C++命名空间特点
1. 普通的命名空间
namespace N1 // N1为命名空间的名称
{
// 命名空间中的内容,既可以定义变量,也可以定义函数
int a;
int Add(int left, int right)
{
return left + right;
}
}
2.命名空间可以嵌套
namespace N2
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N3
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
3. 同一个工程中允许存在多个相同名称的命名空间(编译器会合成到同一个命名空间)
// 编译器最后会合成同一个命名空间中
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
1.1.4 命名空间的三种使用方式
- 加命名空间名称及作用域限定符
int main()
{
printf("%d\n", N::a);
return 0;
}
- 使用using将命名空间中成员引入
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
- 使用using namespace 命名空间名称引入
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
1.2 新增Bool类型关键字
C++在C语 的基本类型系统之上增加了bool
- C++中的bool可取的值只有true和false ,理论上bool只占 1个字节,如果多个bool变量定义在起,可能会各占1个bit,这取决于编译器的实现 ;
- true代表真值,编译器内部 1来表 false代表 真值,编译器内部 0来表示;
- bool类型只有true( 0)和false(0)两个值;C++编译器会在赋值时将 1值转换为true,0值转换为false
int main04() {
//c++中的bool类型 要么是true ,要么是false
//bool 理论上 只占用一个字节 = 8 bit
//如果多个bool变量定义在一起的时候,可能各自占一个bit
//取决于编译器的实现
//true代表真值,编译器内部用1表示, false代表假值,编译器内部用0表示
cout << "C++中的bool类型" << endl;
bool b1 = true;//c++编译器
cout << "sizeof(bool): " << sizeof(bool) << endl;
//c++编译器会在赋值的时候将非0转换为true,
b1 = 10;//要么是1,要么是0
cout << "b1: " << b1 << endl;
//c++编译器会在赋值的时候将非0转换为true,
b1 = -1;
cout << "b1: " << b1 << endl;
//c++编译器会在赋值的时候将0转化给false
b1 = 0;
cout << "b1: " << b1 << endl;
return 0;
}
C++中的bool类型
sizeof(bool): 1
b1: 1
b1: 1
b1: 0
1.3 C++中的const增强
1.3.1 const基础知识
- 用法
int main05() {
cout << "const的基本用法" << endl;
const int a = 10;
int const b = 20;//一样的
//常量指针
const int *c;//常量指针 const修饰的是指针所指向的变量
//代表指针所指向的内存空间,不能被修改
c = &a;
c = &b;
// *c = 30;//常量指针不允许修改指针所执向的内存空间
int a1 = 1;
//指针常量;
int *const d = &a1;//指针常量,const修饰的是指针本身,指向不可变
//常量指针常量;
const int *const e = &a1;//常量指针常量; 指针的指向不能改变,所指向的内存空间也不能改变
return 0;
}
-
含义
表示初始化后不能被修改的常量,可以修饰基础类型,指针等数据类型 -
好处
- 指针函数参数,可以有效的提高代码的可读性,减少bug
- 清楚的区分参数是输入还是输出特性
struct Teacher {
char name[20];
int age;
};
/**
* 参数用于输入特性
* @param pT
* @return
*/
int operatorT1(const Teacher *pT) {//常量指针,表示是一种输入参数,保护原来的参数内容不被修改
// pT->age = 23;
cout << pT->age << endl;
pT = NULL;
}
/**
* 参数用于输出特性
* @param pT
* @return
*/
int operatorT2(Teacher *const pT) {//指针常量,指向地址不能修改,内容可以修改
pT->age = 2;//输出参数
// pT = NULL;
}
1.3.2 C/C++中const的区别
const关键字在C语言编译器器中并不能真正起到锁定变量值的作用,因为我们只要使用一个变量同类型的指针变量p,用p即可直接修改变量的值;
但是在C++编译器中真正做到了锁定变量的值,同样的代码,使用p来修改变量值,然后打印结果,发现变量的值不变。
1.3.3 const和#define相同之处
-
#define
是在编译预处理阶段,只是简单的文本替换 -
const
是由编译器处理的,提供类型检查和作用域检查
1.3.4 const和#define的区别
1.4 三目运算符的增强
#include <iostream>
using namespace std;
int main(void){
int a=3,b=5;
(a<b?a:b)=4;
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
1.5 枚举
enum name {
a,
b,
c
}
3 引用
3.1 引用概念
- 引用是C++的概念
- 引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
- C++ 引用和指针三个主要的不同:
(1) 不存在空引用。引用必须连接到一块合法的内存。
(2) 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
(3) 引用必须在创建时被初始化。指针可以在任何时间被初始化。
3.2 引用的基本用法
- 示例
/**
* 引用的基本用法
* @return
*/
int main01() {
cout << "引用的基本用法" << endl;
// Teacher b;
int a = 10;
// type & name = var;
int &b = a;//b就是一个引用,请不要用C的语法是思考; 含义:就是给a变量取了一个别名叫做b
printf("b:%d\n", b);//使用引用后,b就是a
printf("a:%d\n", a);
b = 100;
printf("b:%d\n", b);//修改b就是相当于改a
printf("a:%d\n", a);
// type * const 引用相当于指针常量
// int &c;//这样定义会报错的,普通的引用必须要依附某个变量,必须初始化
return 0;
}
- 输出结果
引用的基本用法
b:10
a:10
b:100
a:100
3.3 基本类型的引用
- 示例
int myswap1(int a, int b) {//值传递,作用域函数内部,不影响外部的x和y
int tmp = a;
a = b;
b = tmp;
}//完成不了我们的功能
/**
* 使用指针来交换
* @param a
* @param b
* @return
*/
int myswap2(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
/**
* 使用引用的方式来交换
* @param a
* @param b
* @return
*/
int myswap3(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
/**
* 基本类型的引用
* @return
*/
int main02() {
cout << "基本类型的引用" << endl;
int x, y;
x = 10;
y = 20;
myswap1(x, y);
printf("myswap1->x:%d, y:%d \n", x, y);
myswap2(&x, &y);
printf("myswap2->x:%d, y:%d \n", x, y);
//通过myswap3()调用后,a就是x的别名,b就是x的别名,通过操作别,可改变原值
myswap3(x, y);
printf("myswap3->x:%d, y:%d \n", x, y);
return 0;
}
- 输出
基本类型的引用
myswap1->x:10, y:20
myswap2->x:20, y:10
myswap3->x:10, y:20
3.4 复杂类型的引用
- 示例
/**
* 通过指针修改结构体参数
* @param pT
*/
void printT1(const Teacher *pT) {
cout <<"printT1:"<< pT->age << endl;
}
/**
* 通过别名修改结构体参数
* @param pT
*/
void printT2(Teacher &pT) {
cout <<"printT2 start:"<< pT.age << endl;
//pT是一个别名
pT.age = 36;
cout <<"printT2 end:"<< pT.age << endl;
}
/**
*
* @param pT
*/
void printT3(Teacher pT) {//值传递
cout <<"printT3 start:"<< pT.age << endl;
//pT是一个别名
pT.age = 37;
cout <<"printT3 end:"<< pT.age << endl;
}
/**
* 复杂类型引用的使用
* @return
*/
int main03() {
cout << "复杂类型引用的使用" << endl;
Teacher t1;
t1.age = 35;//结构体数据赋值
printT1(&t1);
printT2(t1);//pT 是t1的别名
printT3(t1);//pT是形参, t1 copy一份数据给pT //pT = t1;
printf("t1.age:%d\n", t1.age);
return 0;
}
- 输出
复杂类型引用的使用
printT1:35
printT2 start:35
printT2 end:36
printT3 start:36
printT3 end:37
t1.age:36
3.5 引用的本质
- 引用就是取别名, 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间,引用在C++的内部实现 就是一个常量指针.
- 引用和指针的异同就是:常量指针和指针的异同
- 示例
struct Student {
char name[64];
int age;// 8
int &a;//8
int &b;//8
};
/**
* 通过引用修改(传参的时候不需要取a地址)
* @param a
*/
void modifyA1(int &a) {
//引用
a = 11;
}
/**
* 通过指针常量修改
* @param a
*/
void modifyA2(int *const a) {//指针常量,用于输出参数,地址不可变,地址内容可变
*a = 12;
}
/**
* 通过指针修改
* @param p
*/
void modifyA3(int *p) {
*p = 200;
}
/**
* 引用的本质
*/
void main04() {
cout << "引用的本质思考?" << endl;
int a = 10;
int &b = a;// 定义一个引用b, b像一个常量
modifyA1(a);//函数调用的时候,我们程序员不需要取a的地址
//请思考:对同一内存空间 可以取好几个名字吗?
//引用就是取别名
//从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间
printf("modifyA1->a:%d,b:%d,&a:%p, &b:%p \n",a,b, &a, &b);//这里输出a和b地址一致,说明a和b指向同一个地址
modifyA2(&a);//指针,我们需手动取地址 , 引用在C++的内部实现 就是一个常量指针
printf("modifyA2->a:%d,b:%d,&a:%p, &b:%p \n",a,b, &a, &b);//这里输出a和b地址一致,说明a和b指向同一个地址
modifyA3(&a);//指针,我们需手动取地址
printf("modifyA3->a:%d,b:%d,&a:%p, &b:%p \n",a,b, &a, &b);//这里输出a和b地址一致,说明a和b指向同一个地址
printf("sizeof(Student): %d \n", sizeof(Student));
}
- 输出
引用的本质思考?
modifyA1->a:11,b:11,&a:000000000062fde4, &b:000000000062fde4
modifyA2->a:12,b:12,&a:000000000062fde4, &b:000000000062fde4
modifyA3->a:200,b:200,&a:000000000062fde4, &b:000000000062fde4
sizeof(Student): 88
3.6 引用做函数参数
引用作为函数参数
C++之所以增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能。
C++ 函数传参:
(1)将变量名作为实参和形参。这时传给形参的是变量的值,传递是单向的。如果在执行函数期间形参的值发生变化,并不传回给实参。因为在调用函数时,形参和实参不是同一个存储单元。// 同 c
(2) 传递变量的指针。形参是指针变量,实参是一个变量的地址,调用函数时,形参(指针变量)指向实参变量单元。这种通过形参指针可以改变实参的值。// 同 c
(3) C++提供了 传递变量的引用。形参是引用变量,和实参是一个变量,调用函数时,形参(引用变量)指向实参变量单元。这种通过形参引用可以改变实参的值。
示例如下:
#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
system("pause");
return 0;
}
// 函数定义
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
结果输出:
交换前,a 的值:100
交换前,b 的值:200
交换后,a 的值:200
交换后,b 的值:100
3.7 引用做函数返回值
-
内存四区分配
当引用作为函数的返回值,返回的是一个局部引用(在栈区)时会报错(调用完成后会被回收),不能直接返回
image.png
-
返回栈中的引用和指针的错误示例
int getA1() {
int a;
a = 10;
return a;
}
int &getA2() {
int a;
a = 20;
return a;
//warning: reference to local variable 'a' returned [-Wreturn-local-addr]
//当引用作为函数的返回值,返回的是一个局部引用时会报错,不能直接返回
}
int *getA3() {
int a;
a = 30;
return &a;
//warning: address of local variable 'a' returned [-Wreturn-local-addr]
//当指针引用作为函数的返回值时候,返回的是一个局部变量的地址时候,不能直接返回,
}
int main05() {
int a1 = getA1();
cout << "a1: " << a1 << endl;
//当我们把栈上的引用或者指针返回出来的时候,是有问题的,因为栈的变量和方法调用完之后会被系统自动回收
int a2 = getA2();
// cout<< "a2: " << a2 <<endl;
int *a3 = getA3();
// cout << "a3: " << a3 << endl;
return 0;
}
- 引用作为返回值,必须遵守以下规则:
- 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
- 不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
- 可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
- 返回静态区中的引用示例
/**
*
* @return 返回的是数值
*/
int getG1() {
//静态变量在静态区
static int a = 10;
a++;
return a;
}
/**
*
* @return 返回的是引用
*/
int &getG2() {
static int b = 20;
b++;
cout << "b: " << b << endl;
printf("&b:%p\n", &b);
return b;
}
// 指针 引用 const
int main06() {
cout << "返回static的变量" << endl;
int g1 = getG1();
cout << "g1: " << g1 << endl;
cout << "============================="<< endl;
// getG1() = 100;//不能把函数返回的数值作为左值
int g2 = getG2();//引用可以当右值
cout << "g2: " << g2 << endl;
printf("&g2:%p\n", &g2);
cout << "============================="<< endl;
getG2() = 200;//引用也可以作为左值
cout << "============================="<< endl;
getG2();
return 0;
}
返回static的变量
g1: 11
=============================
b: 21
&b:0000000000409014
g2: 21
&g2:000000000062fde8
=============================
b: 22
&b:0000000000409014
=============================
b: 201
&b:0000000000409014
Process finished with exit code 0
3.8 指针引用
3.9 常引用
4 函数
4.1 inline内联函数
- 定义
- 内联函数 声明时候必须实现,没办法分开,如果分开了C++编译器会取消内联
- 内联函数必须放在调用它的方法的前面
- C++编译器不一定会允许函数内联
- 限制:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取地址操作
- 函数内联声明必须在调用语句之前
#include <iostream>
using namespace std;
//定义一个方法
void printA();//方法声明
//定义一个宏
#define MYFUNC(a, b) ((a) < (b)) ? (a) : (b)
/**
* C++ 内联函数 inline
*/
inline void printB() {
//1. 内联函数声明的时候必须实现,没办法分开,如果分开了C++编译器会取消内联
//2. 内联函数必须放在调用它的方法的前面
//3. C++编译器不一定会允许函数内联
int a = 10;
cout << "a: " << a << endl;
//有循环不会内联
// for(int i =0; i < 10000; i++){
//
// }
//if else switch 条件判断的语句过多,取消内联
}
// 当我们的函数本身代码执行的代价 大于函数调用压栈出栈的开销的时候,内联将没有任何意义
//g++ __attribute__((always_inline)) 属性 ,强制内联
int main01() {
cout << "C++的函数" << endl;
printA();
//C语言中
MYFUNC(1, 2);
printB(); //把printB的代码嵌入到调用点
int a = 11;
cout << "a: " << a << endl;
return 0;
}
/**
* 方法实现
*/
void printA() {
//定义实现
cout << "printA" << endl;//C++编译器有可能会给你内联
}
4.2 默认参数和占位参数
4.2.1 默认参数
- 若你填写参数,使用你填写的,不用默认的
- 在默认参数规则中,如果默认参数出现,那么右边都必须有默认参数
- 示例
//1. 若你填写参数,使用你填写的,不用默认的
void myPrint(int x = 1) {
cout << "x: " << x << endl;
}
//2. 在默认参数规则中,如果默认参数出现,那么右边都必须有默认参数
//void myPrint2(int a,int b,int c = 1,int d= 2,int e) 不可以这么玩
void myPrint2(int a, int b, int c = 1, int d = 2) {
cout << "a: " << a << " b: " << b << " c: " << c << " d: " << d << endl;
}
int main02_1() {
cout << "函数的默认参数" << endl;
myPrint(4);
myPrint();
cout << "============================="<< endl;
myPrint2(1, 2, 3, 4);
myPrint2(5, 6);//可只传部分连续的参数
myPrint2(7, 8, 9);
return 0;
}
- 输出
函数的默认参数
x: 4
x: 1
=============================
a: 1 b: 2 c: 3 d: 4
a: 5 b: 6 c: 1 d: 2
a: 7 b: 8 c: 9 d: 2
4.2.2 占位参数
- 占位参数 只有参数类型,没有参数名,但是调用函数时候占位参数也必须传值
/**
* 函数占位参数
* @param a
* @param b
* @return
*/
void myPrint3(int a, int b, int) {//占位参数 只有参数类型,没有参数名
cout << "a: " << a << " b: " << b << endl;
}
int main02_2() {
cout << "函数占位参数" << endl;
// myPrint3(1,2);//报错 调不起来
myPrint3(1, 2, 3);
return 0;
}
- 意义何在?
- 为以后的程序留下线索
- 兼容C语言中可能出现的不规范写法
4.3 函数重载
4.3.1 函数重载概念
- 函数名一致
- 参数不同(个数/类型)
void test(int a) {
cout << "a: " << a << endl;
}
void test(char *a) {
cout << "a: " << a << endl;
}
void test(int a, int b) {
cout << "a: " << a << " b: " << b << endl;
}
//int test(int a,int b){
// cout << "a: " <<a<< " b: " << b<< endl;
// return 1;
//}
//重载
/*
* 1. 函数名一致
* 2. 参数不同(1,个数,类型)
*/
int main03_1() {
cout << "函数重载" << endl;
test(1);
test("aaaaaa");
test(1, 2);
return 0;
}
4.3.2 函数重载的调用准则
4.3.3 函数重载遇上函数默认参数
- 示例
void test1(int a, int b) {
cout << "a: " << a << " b: " << b << endl;
}
/**
* 该重载函数包含了一个默认参数
* @param a
* @param b
* @param c
*/
void test1(int a, int b, int c = 3) {
cout << "a: " << a << " b: " << b << endl;
}
void test1(int a) {
cout << "a: " << a << endl;
}
int main03_2() {
cout << "" << endl;
// test1(1,2);//函数调用时,会产生二义性,无法调用
return 0;
}
4.3.4 函数重载和函数指针结合
void myfunc5(int a) {
cout << "a: " << a << endl;
}
void myfunc5(char *a) {
cout << "a: " << a << endl;
}
void myfunc5(int a, int b) {
cout << "a: " << a << " b: " << b << endl;
}
void myfunc5(char *a, char *b) {
cout << "a: " << a << " b: " << b << endl;
}
//typedef声明一个函数类型:myTypeFunc5
typedef void (myTypeFunc5)(int a, int b);//声明了一个函数类型
//typedef声明一个函数指针类型
typedef void (*myPFunc5)(int a, int b);//一个函数指针只能指向一个具体的函数
//typedef void (*myPFunc5)(char* a,char* b);
typedef int SIZE_OF;
int main03_3() {
cout << "函数指针与函数重载" << endl;
SIZE_OF a;
//初始化函数指针
myTypeFunc5 *func = NULL;
//将函数指针指向函数
func = myfunc5;
//通过函数指针调用函数
func(1, 2);
cout << "=============================" << endl;
//函数指针初始化
myPFunc5 func2 = nullptr;
func2 = myfunc5;
func2(1, 2);
// func(1);
cout << "=============================" << endl;
char buf1[] = "aaaaa";
char buf2[] = "adfwer";
// func2(buf1, buf2);//这里不能调用,参数为指针说明调用的是myfunc5(char *a, char *b),必须定义一个该参数类型的函数指针
return 0;
}
- 输出
函数指针与函数重载
a: 1 b: 2
=============================
a: 1 b: 2
=============================
网友评论