IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++笔记(三)---变量 -> 正文阅读

[C++知识库]C++笔记(三)---变量

一、概述

变量声明规定了变量的类型和名字,在这一点上定义与之相同。除此之外定义还申请存储空间,也可能会为变量赋初始值。变量能且只能被定义一次,但是可以被多次声明。如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量:
extern int i;????????//声明i而非定义i
int j;? ? ? ? ? ? ? ? ? ?//声明并定义j
1)extern修饰变量的声明
如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。
2)extern修饰函数的声明
如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。
3)extern修饰符可用于指示C或者C++函数的调用规范。
比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

变量的声明和定义
从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存。
函数的声明和定义
声明:一般在头文件里,对编译器说:这里我有一个函数叫function() 让编译器知道这个函数的存在。
定义:一般在源文件里,具体就是函数的实现过程 写明函数体。

二、const

? ? 利用const进行修饰的变量的值在程序的任意位置将不能再被修改,就如同常数一样使用。任何修改该变量的尝试都会导致编译错误。因为常量在定义后就不能被修改,所以定义时必须初始化。? ? 非const变量在别的源文件要引用直接用extern int var就行了 但是const变量在别的源文件要引用必须本文件和源文件都要加extern

非const变量:
file1.cpp:int var=10;
file2.cpp:extern int var;

const变量:
file1.cpp:extern const int var=10;
file2.cpp:extern const int var;
因为非const变量默认为extern。要使const变量能够在其他文件中访问,必须在文件中显式地指定它为extern。

指向常量的指针 const int * p=&var 或者int const * p=&var不能通过该指针去修改所指向的变量的值 (可能会有其他方式)?将一个const对象赋给一个普通的非const指针会导致编译错误。

常量指针int *const p=&var?指针本身的地址是个常量对象 不能更改,不能中途重新赋值 必须初始化。

非常量引用不能绑定在常量上,常量引用既可以绑定在常量上也可以绑定在非常量上,不能通过常量引用去改变被引用的值。

const类成员函数
1.不能修改数据成员和调用非const成员函数 (静态成员变量和静态成员函数除外)否则会报错 ?相当于把this指针变成指向常量版本的常量指针
2.在函数的声明处末尾和实现处末尾都要加const关键字

3.可以通过尾巴是否有const实现重载

4.const 关键字不能与 static 关键字同时使用,因为 static 关键字修饰静态成员函数,静态成员函数不含有 this 指针,即不能实例化,const 成员函数必须具体到某一实例

const类型转换
?? ?
const_cast去除指向常数对象的指针或引用的常量性。

const int *a = &p
int* b = const_cast<int*>(a);

三、static

1、静态全局变量
在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。举一个静态全局变量的例子:

#include<iostream>
using namespace std;
static int n; ?//定义静态全局变量
void fn()
{
? ? n++;
? ? cout<<n<<endl;
}
int main(void)
{
? ? n = 20;
? ? cout<<n<<endl;
? ? fn();
? ? return 0;
}

?静态全局变量有以下特点:
?该变量在全局数据区分配内存;
?未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
?静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的; 
静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:
  一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
???????静态全局变量不能被其它文件所用;
???????其它文件中可以定义相同名字的变量,不会发生冲突;
可以将上述示例代码改为如下:

//File1
#include<iostream>
using namespace std;
void fn();
static int n; ?//定义静态全局变量
int main(void)
{
? ? n = 20;
? ? cout<<n<<endl;
? ? fn();
? ? return 0;
}

//File2
#include<iostream>
using namespace std;
extern int n;
void fn()
{
? ? n++;
? ? cout<<n<<endl;
}

?编译运行这个程序会发现上述代码可以分别通过编译,但运行时出现错误。
2、静态局部变量
在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。?

#include<iostream>
using namespace std;
void fn();
int main(void)
{
? ? fn();
? ? fn();
? ? fn();
? ? return 0;
}
void fn()
{
? ? static int n = 10;
? ? cout<<n<<endl;
? ? n++;
}

通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
  但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
  静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。静态局部变量有以下特点:
????(1)该变量在全局数据区分配内存;
????(2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
????(3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
????(4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

全局变量和static变量的区别

1、全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。

这两者在存储方式上并无不同。区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。

2.static函数与普通函数有什么区别?
static函数与普通的函数作用域不同。仅在本文件中。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
static函数与普通函数最主要区别是static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)

static的作用

1.隐藏。(static函数,static变量均可)
当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。比如同时编译两个源文件,一个是a.c,另一个是main.c。

//a.c
char a = 'A'; // global variable
void msg()
{
? ? ?printf("Hello\n");
}
//main.c
int main()
{
? ? ?extern char a; // extern variable must be declared before use
? ? ?printf("%c ", a);
? ? ?(void)msg();
? ? ?return 0;
}

?程序的运行结果是:A Hello

为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏。

2.第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量。如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。

#include <stdio.h>
int fun(){
? ? static int count = 10; 
//在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,以后每次进入该函数,a就不会被再次
初始化了,仅进行自减1的操作;在static发明前,要达到同样的功能,则只能使用全局变量: ? ?
? ? return count--; ?
}
int count = 1;
int main(void)
{
? ? ?printf("global\t\tlocal static\n");
? ? ?for(; count <= 10; ++count)
? ? ? ? ? ? ? ?printf("%d\t\t%d\n", count, fun());
? ? ?return 0;
}

?程序的运行结果是:
global??local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1

---基于以上两点可以得出一个结论:把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,?限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。

3.static的第三个作用是默认初始化为0(static变量)
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’;太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’;不妨做个小实验验证一下。

?#include <stdio.h>?
int a;?
int main()
{
? ? ?int i;
? ? ?static char str[10];
? ? ?printf("integer: %d; string: (begin)%s(end)", a, str);
? ? ?return 0;
}

程序的运行结果是:
integer: 0; string: (begin) (end)?

最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
4.static的第四个作用:C++中的类成员声明static(有些地方与以上作用重叠)
?在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。 ?????
(2)不能将静态成员函数定义为虚函数。 ?????
(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。
(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X Window系统结合,同时也成功的应用于线程函数身上。?(这条没遇见过)??
(5)static并没有增加程序的时空开销,相反还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。 ?????
(6)静态数据成员在<定义或说明>时前面加关键字static。 ?????
(7)静态数据成员是静态存储的,所以必须对它进行初始化。?(程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)?
(8)静态成员初始化与一般数据成员初始化不同:
初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;????????
初始化时使用作用域运算符来标明它所属类;
静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>
(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:静态成员为父类和子类共享,但重复定义了静态成员,这会不会引起错误呢?不会,编译器采用了name-mangling 用以生成唯一的标志。

?静态变量什么时候初始化

初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。
静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存,但在C和C++中静态局部变量的初始化节点不太一样。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。
而在C++中,初始化时在执行相关代码时才会进行初始化,主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象首次用到时才会进行构造,并通过atexit()来管理。在程序结束时按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的。

static的成员函数

静态成员函数主要为了调用方便,不需要生成对象就能调用。
使用
?? ??? ?0.定义静态成员函数,直接使用static关键字修饰即可

static int get_count()
{return count;}

? ? ? ? 1.可以通过类名直接访问类的公有静态成员函数

cout<<"rec count: "<< Rectangle::get count()<<endl;
?? ??? ?2. 可以通过对象名访问类的公有静态成员函数

cout<<" rec count: " <<my_rec3.get_count()<<endl;
特性
?? ??? ?1.-静态成员函数是类的一个特殊的成员函数-静态成员函数属于整个类所有,没有this指针
?? ??? ?2.静态成员函数只能直接访问静态成员变量和静态成员函数
?? ??? ?3.

静态成员变量

不管定义多少个类对象,静态数据成员都共享分配在全局数据区的内存,所以节省了存储空间。一旦某变量需要改变时,只要改变一次,则所有类对象的该变量全改变过来了。有一些状态是和类本身相关的而不是和对象相关的 这些状态数据可以用静态成员变量去表达。C++类的静态成员变量需要在类外初始化,静态成员变量在类中仅仅是声明。没有定义,编译器暂时不会给它分配内存
所以要在类的外面定义,实际上是给静态成员变量分配内存。
静态成员变量在程序内部位于全局数据区 (Type className::VarName = value),需要在类外单独分配空间。如果没有在类外定义的话 编译的时候会出现对这个变量未定义的引用。可以通过类名直接访问公有静态成员变量。

静态成员与普通成员的区别

1)生命周期
静态成员变量从类被加载开始到类被卸载,一直存在;普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;
2)共享方式
静态成员变量是全类共享;普通成员变量每个对象都有自己的拷贝;
3)定义位置
普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;
4)初始化位置
普通成员变量在类中初始化;静态成员变量在类外初始化;
5)默认实参
可以使用静态成员变量作为默认实参

四、变量的volatile与atomic

volatile 关键字是一种类型修饰符, 用它声明的类型变量表示可以被某些编译器末知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法: intvolatile vInt;

当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:
volatile inti=10;
inta=i;
//其他代码,井末明确告诉编译器,对i进行过操作
intb=i;
volatile 指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以volatile可以保证对特殊地址的稳定访问。在VC6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。

不只是内嵌汇編操纵栈这种方式属于编译无法识别的变量改变,更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程visible。一般volatile用在如下的几个地方:
1) 中断服务程序中修改的供其它程序检测的变量需要加volatile;
2) 多任务环境下各任务间共享的标志应该加volatile;
3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。

原子操作(atomic operation)指的是由多步操作组成的一个操作。该操作要么执行完所有步骤,要么一步也不执行,不可能只执行某个步骤。原子操作就是不可被中断的一个或一系列操作。

#include <iostream>
#include <ctime>
#include <vector>
#include <thread>
#include <atomic>
std:atomic<size_t> count(0);
void threadFun{
for(int i = 0;i<1000; i++)
    count++;}
int main(void){
    clock_t start_time = clock();
    //启动多个线程
    std:vecto<std:thread> threads;
    for(int i=0;i<10;i++)
        threads.push_back(std::thread(threadFun));
    for(auto&thad: threads)
        thad join();:
    //检测count是否正确 1000*10 = 10000
    std:cout << "count number:"<<count<<std:endl;
    clock_t end_time=clock();
    std:cout<<"耗时: " << end_time-start_time << "ms" << std:endl;
    return 0;
}

?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-28 12:13:13  更:2021-10-28 12:14:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:53:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码