c++学习之c++对c的扩展2

简介: c++学习之c++对c的扩展2

1.c/c++中的const

1 const概述

const 修饰的对象为一个常量,不能被改变

2 c/c++中const的区别

c语言中的const1 const修饰的局部变量,存在栈区,虽然不能通过const修饰的变量去修改栈区内容,但是可以 通过地址去修改

const修饰的全局变量是保存在常量区,不能通过变量名去修改.也不能通过地址去修改。

const修饰的全局变量,如果其他文件想使用,直接extern声明外部可用即可。

c中的:

在main.c中

#include <stdio.h>
const int b = 10;//const修饰的全局变量保存在常量区
 void test03()
 {
 extern const int num;//声明num是外部可用的
 printf("num=%d\n",num);
 }
 //const修饰的全局变量
 void test02()
 {
 //b = 100;
 int *p = &b;
 *p = 100;//错误的 不能修改常量区的内容
 printf("b=%d\n",b);
 }
 //const修饰的局部变量
 void test01()
 {
 //在c语言中const修饰的变量保存在栈区
 const int a = 10;
 //a = 100;
 int *p = &a;
 *p = 100;
 printf("a=%d\n",a);
 }
 int main()
 {
 test03();
 return 0;
 }

onst int num = 1;

c++中的const:

1 const修饰的局部变量赋值常量时,局部变量保存在符号表中,修改不了,是一个常量

2 const修饰的全局变量保存在常量区,不能被修改

3 const修饰的全局变量默认是内部链接属性,加上extern修饰变成外部链接属性

mian.cpp

#include <iostream>
 using namespace std;
 const int b = 1;
 void test03()
 {
 extern const int num;
 cout << num << endl;
 }
 void test02()
 {
 //const修饰的全局变量存在常量区
 int *p = (int *)&b;
 *p = 100;//错误的
 cout << b<< endl;
 }
 //c++中const修饰的局部变量
 void test01()
 {
 //c++中const修饰的局部变量存在符号表中
 const int a = 10;
 //a = 100;
 //对const修饰的局部变量取地址 编译器会产生一个临时变量来保存a的地址
 // int tmp = a; int *p = &tmp
 int *p = (int *)&a;
 cout << a << endl;
}
int main()
 {
 test03();
 return 0;
 }

extern const int num = 1;// const修饰的全局变量默认是内部链接属性.

c/c++中的const异同

相同的点:

c和c++中的const修饰的全局变量都是保存在常量区,不能被修改

不同的点:

c语言中const修饰的局部变量赋值为常量时,,局部变量保存在栈区,可以被指针修

c++中,const修饰的局部变量赋值为常量时,局部变量保存符号表中,不能被修改

c语言中const修饰的全局变量默认是外部链接属性

c++语言中const修饰的全局变量默认是内部链接属性

c++中const修饰的变量,分配内存情况

const修饰的全局变量在常量区分配了内存

对const修饰的局部变量赋值为常量时,对其取地址,会在栈区分配临时的内存空间

const修饰的局部变量赋值为变量时,局部变量保存在栈区

const修饰的局部变量时一个自定义变量,也是在栈区分配内存

只有一种情况,const'修饰的局部变量被赋值为常量时,这个局部变量保存在符号表中,

符号表后中的变量,编译器直接访问,不会为其开辟空间。

#include <iostream>
 using namespace std;
 const int a = 1;//const修饰的全局变量在常量区分配了内存
 void test01()
{
 const int a = 10;//const修饰的局部变量赋值为常量没有分配内存,存在符号化表中
 int *p = (int *)&a;//对const修饰的局部变量赋值为常量的变量取地址 会分配一个临
时的空间 int tmp =a *p =&tmp
 }
void test02()
 {
 int b = 2;
 //const修饰的局部变量赋值变量时 局部变量存在栈区
 const int a = b;
 int *p = (int *)&a;
 *p = 100;
 cout << a << endl;
 }
 struct stu
 {
 int a;
 int b;
 };
 void test03()
 {
 //const修饰的变量为自定义变量时,保存在栈区
 const struct stu obj = {1,2};
 struct stu *p = (struct stu *)&obj;
 p‐>a = 3;
 p‐>b = 4;
cout << obj.a << " " << obj.b << endl;
 }
 int main()
 {
test03();
return 0;
 }

尽量以const替换define

有两点原因:

1. const修饰的全局变量或const修饰的局部变量赋值为常量,是有类型的,而define的

宏没有 类型

2. const修饰的全局变量或const修饰的局部变量赋值为常量有作用域的,而define的

宏没有作用域

#include <iostream>
 using namespace std;
 namespace A
 {
 const int max = 1024;
 const short max1 = 1024;
 #define MAX 1024
 }
 // 宏没有作用域 宏没有类型(int)
 void fun(int a)
 {
 }
 void fun(short a)
 {
 }
 void test01()
 {
 cout << A::max << endl;
 cout << MAX << endl;
 fun(MAX);//void fun(int a)
 fun(A::max);//void fun(int a)
 fun(A::max1);//void fun(short a)
 }
 int main()
30 {
31
32 return 0;
33 }

宏定义的变量是做替换的,并且是全局的没有作用限制,但是const修饰的变量为常量是有范围的,范围即为原来变量的范围。

2.引用

引用的作用为给变量取别名。

引用的基本用法

原类型 &别名 = 旧名  这里的&不是取地址符,而是引用。

1965c23f7e4d4233b0119c5a711068e1.png

在这里取别名后,两者的地址是一样的,且对a操作,b也会跟着操作。

49b0c99fca714ea68069c749545bde2f.png

注意事项:

引用一旦初始化,不能更改引用的指向

引用定义时必须初始化

不能引用NULL

引用可以引用任意类型包括数组

&在等号的左边是引用,在等号的右边是取地址

#include <iostream>
 using namespace std;
 void test01()
 {
 int a = 10;
 //引用一旦初始化之后不能改变引用的标识
 int &b = a;
 b = 100;
 cout << a << endl;
int c = 1;
 //b = c; 代表把c的值赋值给b 不是给c取别名为b
 //int &d; 引用定义时必须初始化
 }
 void test02()
 {
 int a[5] = { 1,2,3,4,5 };
 //int(&arr)[5] = a;
 typedef int ARR[5];
 //type & 别名 = 旧名
 ARR & arr = a;
 for (int i = 0; i < 5; i++)
 {
 cout << arr[i] << " ";
 }
 cout << endl;
 }
 int main()
 {
 test02();
return 0;
 }

变量的引用并不会为他开辟空间,只想相当于多了个名字。

函数的引用:

注意:

引用可以作为函数的形参

函数调用时 就是  &实参=形参  实参的改变影响形参,我们不用指针也可以做到这一点。

不能返回局部变量的引用

//指针方式交换
 void swap(int *x, int *y)
 {
 int tmp = *x;
 *x = *y;
 *y = tmp;
 }
 void test01()
 {
 int a = 10;
 int b = 20;
 swap(&a,&b);
 cout << a << " " << b << endl;
 }
//引用交换
 void swap_ref(int &x, int &y)// int &x =a, int &y =b)
 {
 int tmp = x;
 x = y;
 y = tmp;
 }
int main()
 {
 int a = 10;
 int b = 20;
 swap_ref(a, b);
 swap(&a,&b);
 }

引用的本质

引用的本质是一个指针常量

type &b = a; 编译器底层这么实现的:

type *const b = &a;

#include <iostream>
 using namespace std;
void test01()
 {
 int a = 10;
 int &b = a;//编译器优化 int* const b = &a
 //指针常量 不能改变指针变量的指向
 // b =0x100;
 b = 1000;// *b =1000
 }
 void fun(int *&q)//int *&q = p ==> 编译器 int * const q =&p
 {
 }
 void test02()
 {
 int *p = NULL;
 fun(p);
 } 

指针的引用

套用引用公式: type &q = p

假设:

type为指针类型

 void fun ( int * & q ) // int* &q = p

 {

 }

 void test ()

 {

 int * p = NULL ;

 fun ( p );

 }

5 常量引用

const type &p = q;

常量引用代表不能通过引用去修改引用标识的那块空间

#include <iostream>
 using namespace std;
 void test01()
 {
 int a = 10;
 // const修饰的是引用& 不能通过引用去修改引用的这块空间的内容
 const int &b = a;
 //b = 1000;//err
 }
 void test02()
 {
 //int &b = 100;//不能引用常量
 const int &b = 1;//int tmp =1 ,const int &b= tmp
 }
 int main()
 {
 return 0;
 }

内联函数

内联函数的基本概念

在 c++ 中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。

内联函数具有普通函数的所有行为。唯一不同之处在于 内联函数会在适当的地方像预定义宏

一样展开,所以不需要函数调用的开销。 因此应该不使用宏,使用内联函数。 在普通函数 ( 非成员函数 ) 函数前面加上 inline 关键字使之成为内联函数。

但是必须注意必须

函数体和声明结合在一起,否则编译器将它作为普通函数来对待。

inline void func(int a);

以上写法没有任何效果,仅仅是声明函数,应该如下方式来做 :

inline int func(int a){return ++;}

注意 : 编译器将会检查函数参数列表使用是否正确,并返回值 ( 进行必要的转换 ) 。这些事预

处理器无法完成的。

但是函数调用就会占用空间:,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压

栈,跳转,返回的开销。我们可以理解为内联函数是以空间换时间。

内联函数就是继承了宏函数的高效,并且不会出错,还可以当成类的成员函数用

2 宏函数和内联函数的区别

宏函数的替换是发生在 预处理阶段

内联函数的替换是发生在 编译阶段

宏函数容易出错,内联函数不会

内联函数和宏函数一样,都省去了调用函数的开销。

举个例子

//宏函数
#define MYCOMPARE(a,b) (a)<(b)?(a):(b)
//内联函数
 inline int mycpmpare(int a, int b)
 {
 return a < b ? a : b;
 }
 void test02()
 {
 int a = 1;
 int b = 5;
 //int c = MYCOMPARE(++a, b);// ++a<b?++a:b
 int c = mycpmpare(++a, b);
 cout << c << endl;
 }

类的成员函数默认编译器会将它做成内联函数

class Person{
public:
 Person(){ cout << "构造函数!" << endl; }
 void PrintPerson(){ cout << "输出Person!" << endl; }
 }

类里的成员函数默认为内联函数。

内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议 ,如果你没有将函数声明为

内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的

函数


相关文章
|
2月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
279 63
|
4月前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
83 0
|
2月前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
|
2月前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
23 1
|
3月前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
3月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
247 0
|
5月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
5月前
|
人工智能 分布式计算 Java
【C++入门 一 】学习C++背景、开启C++奇妙之旅
【C++入门 一 】学习C++背景、开启C++奇妙之旅
|
5月前
|
存储 自然语言处理 编译器
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
|
5月前
|
小程序 C++
【C++入门 二 】学习使用C++命名空间及其展开
【C++入门 二 】学习使用C++命名空间及其展开