C++基础入门(2)

简介: C++基础入门

7 共用体的使用

7.1 共用体的内存布局

共用体使不同的类型变量共享同一段内存单元,所以共用体只能存储一个数据成员的值,

共用体的大小等于最大成员的大小。在程序中改变共用体的一个成员值,其它成员值也随之改变


7.2 判断系统字节序

大小端,大小字节序,高低字节序,或Big&Little Endian,表示数据在内存中的存放模式


大端:网络字节序 小端:主机字节序


低字节序:低字节保存在内存的低地址中


高字节序:低字节保存在内存的高地址中


#include <iostream>
//共用体:
int checkByteOrder(){
  union 
  {
    int data;
    char flag;
  }subject;
  subject.data = 1;
  return (subject.flag == 1);
};
int main(){
  if (checkByteOrder() == 1){
    printf("当前系统为低字节序\n");
  }
  else{
    printf("当前系统为高字节序\n");
  }
  system("pause");
  return 0;
}

8 枚举的使用

8.1 枚举&枚举变量

枚举,本质上是一组被命名的常数集合

枚举变量,由枚举类型定义的变量


// 定义枚举类型kWeek
// 包括枚举和const常量:名称前加小写k,且除去开头k外每个单词开头字母均大写
enum Week {kSun, kMon, kTue, kWed, kThu, kFri, kSat};
// 定义枚举变量
Week weekday1, weekday2;
enum {kSun,kMon,kTue,kWed,kThu,kFri,kSat} weekday1, weekday2;

注意:枚举变量大小,等于枚举类型所占内存大小


8.2 枚举的内存布局

枚举类型本身是不占内存的,但是枚举值占用内存的。枚举默认是用int类型来存储的,32位系统下占4个字节。#define 定义的常量,在预编译时替换。而enum定义的常量,是在运行时替换,根据标识去常量区获取对应的值


可以通过继承方式改变枚举的大小,例如:


//TypeChar 类型变量大小占用1字节
enum TypeChar : unsigned char
{};


具体实现:


enum EnumInt{
  kMon = 1, kTue = 2, kWed = 3
};
enum EnumChar : unsigned char {
  kRed = 0x00,
  kYellow,
  kBule = 0xff
};
//sizeof(EnumInt)  = 4
//sizeof(EnumChar) = 1

扩展:枚举的范围


如果某个枚举符的值均为非负值,该枚举的取值范围就是[0~2^k-1]


如果存在负的枚举符值,则该枚举的取值范围就是[-2k~2k-1]


k 是能使所有枚举值位于此范围内最小2的幂


举例1:enum kE1{ a=2, b=4 };kE1均为正值,能使所有枚举值位于此范围内最小2的幂为 3 ,因此k的值为2,于是kE1的取值范围为[0,7]


举例2:enum kE2{ a=-2, b=4 };kE2有正有负,那么满足条件的k的值为3,kE1的取值范围是[-8,7]


9 指针与引用

9.1 指针&引用

名称 内存大小 布局 说明
指针 四个字节 二进制数据储存 是用来控制内存地址的变量,它指向单个对象的地址
引用 四个字节 二进制数据储存 引用是一个指针常量
指针数组 元素所占内存的大小 与元素的储存结构有关 是一个只包含指针元素的数组
数组指针 四个字节 二进制数据储存 是一个指针变量,它指向一个数组
指向指针的指针 四个字节 二进制数据储存 是一个指针链。第一个指针包含了第二个指针的地址,第二个指针指向实际值

说明:以上内存大小均为 32 bit 位系统


9.2 深拷贝&浅拷贝

深拷贝:被访问的数据,在每个单元中都有自己的一份


浅拷贝:两个函数通过拷贝它们共享的数据指针来工作


#include <iostream>
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
class Person {
public:
  explicit Person(const int height) {
  p_height_ = new int(height);
  }
  Person(const Person& p) {
  //p_height_ = p.p_height_;
  p_height_ = new int(*p.p_height_);
  }
  ~Person() {
  if (p_height_ != NULL){
    delete p_height_;
  }
  }
private:
  int* p_height_;
};
int main() {
  Person p1(180);
  Person p2(p1);
  system("pause");
  return 0;
}


explicit关键字:被修饰的类构造函数,不能进行隐式类型转换,如下图所示:


扩展:new/delete 和 malloc/free 区别


1、malloc/free是C/C++标准库的函数,new/delete是C++操作符


2、返回值类型不同:new操作符内存分配成功时, 返回的是对象类型的指针,而malloc返回的是void*指针, 需要强转将指针转换为所需类型


3、定制内存大小不同:malloc/free需要手动计算类型大小,而new/delete编译器可以自己计算类型大小


4、分配失败时返回值:new内存分配失败时会直接抛bac_alloc异常, 它不会返NULL, malloc分配内存失败时返回NULL


5、malloc/free只是动态分配内存空间/释放空间,而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)


10 存储分类说明符

static、register、extern都是用来变量存储说明的关键字,选择适当的存储分类符,不仅能够提高变量的访问效率,而且还能使内存的存储空间更有效


10.1 auto

auto 中文释义:自动的


作用:使变量在离开作用域自动销毁,一般情况下可省略


#include <iostream>
int main(){
    auto int a = 2;        //离开main函数,变量a自动销毁
    return 0;
}


10.1 static

static 中文释义:静态的


作用:定义的变量只能接受一次初始化,不能接受第二次


#include <iostream>
void func(){
    static int a = 10;
    a++;
    std::cout << a << std::endl;
}
int main(){
    func();    //初始化a为10,输出a为11
    func();    //不接受初始化,a为11,输出a++,等于12
    system("pause");
    return 0;
}

10.2 register

register 中文释义:寄存器


寄存器类型,寄存器是计算机内距离CPU最近的容器,将变量尽可能定义在寄存器里,使变量读取更加快速,提高程序效率。缺点是寄存器空间有限, 常用于频繁使用的变量


是否一定会放在寄存器里


register int often_use_num = 0;


10.3 extern

extern 中文释义:外部变量


它属于变量声明,extern int n和int n的区别就是,告知编译器,int类型变量n定如有调用去其它文件中查找定义


//call.cpp
#include <iostream>
void func(){
  extern int n;
  std::cout << n << std::endl;
}
int main(){
  func();
  system("pause");
  return 0;
}
//definition.cpp
int n = 101;

11 存取限定符

11.1 const

const 中文释义:常量


指定一个语义约束,编译器会强制实施这个约束,使某值保持不变的


场景:修饰普通变量,指针变量,参数传递和函数返回值


const int val = 5;
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a; 
p1 = &b; //正确
//*p1 = 100;  报错
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确


11.2 volatile

volatile 中文释义:不稳定的


使用 volatile 声明的变量,系统总是重新从它所在内存读取数据,简单地说就是防止编译器对代码进行优化


场景:多任务环境下各任务之间的共享标志


#include <stdio.h>
void main()
{
    volatile int i = 10;
    int a = i;
    // 下面汇编语句的作用就是改变内存中 i 的值
    // 但是又不让编译器知道
    printf("i = %d", a);
    __asm {
        mov dword ptr [ebp-4], 20h
    }
    int b = i;
    printf("i = %d", b);
}


Release 模式下,变量i声明是否声明volatile对结果有显著影响


变量未声明volatile时:编译器对代码进行了优化,输出结果不正确

变量声明volatile时:关键字发挥了作用

volatile的意义在于多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程可见


12 inline关键字

12.1 引入inline关键字

在c/c++中,引入inline修饰符,为了解决一些频繁调用的小函数大量消耗栈内存问题,表示为内联函数


#include <stdio.h>
//函数定义为inline即:内联函数
inline char* dbtest(int a) {
 return (i % 2 > 0) ? "奇" : "偶";
} 
int main()
{
int i = 0;
for (i=1; i < 100; i++) {
    printf("i:%d    奇偶性:%s /n", i, dbtest(i));    
}
}

内联函数作用就是在每个for循环内部任何调用dbtest(i)的地方都换成了(i%2>0)?”奇”:”偶”,避免了频繁调用函数对栈内存重复开辟带来的消耗


12.2 inline内联优缺点

相比普通函数,内联函数效率更高,因为内联函数通过复制代码,节省了函数调用的时间


inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且部分编译器不支持递归内联


以下情况使用inline不会起作用:


函数体内代码长,使用内联将导致内存消耗代价较高

函数体出现循环,执行函数体内代码的时间要比函数调用的开销大

12.3 宏函数&内联的区别

内联函数可以被调试,而宏函数不可以


宏定义没有类型检查,无论对错都是直接替换


内联函数在编译时会进行类型检查


宏定义不是函数,而是在预编译的时候把宏名用宏体替换,本质就是字符串替换


而内联函数本质上是一个函数,有参数列表和返回值,它在编译进行代码插入,编译器会在调用内联函数的地方,把函数内容展开,省去函数调用开销,来提高效率


相关文章
|
3月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
57 2
C++入门12——详解多态1
|
3月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
41 3
|
3月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
58 2
|
3月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
94 1
|
3月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
102 1
|
3月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
36 1
|
3月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
58 1
|
3月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
76 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
3月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
36 0
|
3月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
40 0