C++核心编程(1)

简介: C++核心编程

13 C++类型转换

C风格转换【(TYPE)EXPRESSION】可在任意类型之间转换,且不易查找。所以C++引进了四种类型转换操作符,解决以上问题。


类型 主要用途
static_cast 静态类型转换
dynamic_cast 子类和父类间多态类型转换
const_cast 去掉const属性转换
reinterpreter_cast 重新解释类型转换

13.1 static_cast

static_cast<目标类型>(标识符)

静态,即在编译期内就可完成其类型转换,最经常使用

#include<iostream>
int main(){
  double pi = 3.1415926;  
  int num1 = (int)pi;   // C语言旧式类型转换
  int num2 = pi;        // 隐式类型转换
    //C++静态类型转换
    //在编译期完成基本类型的转换,且有类型检查
  int num3 = static_cast<int> (pi);  
  std::cout << "num1 = " << num1 << std::endl;
  std::cout << "num2 = " << num2 << std::endl;
  std::cout << "num3 = " << num3 << std::endl;
    system("pause");
  return 0;
}

13.2 dynamic_cast

dynamic_cast<目标类型>(标识符)

用于多态中父子类之间的多态转换,转换类指针时,基类需要虚函数

#include<iostream>
class Animal{
public:
  virtual void shout() = 0;
};
class Dog : public Animal
{
public:
  virtual void shout(){
    std::cout << "汪汪" << std::endl;
  }
  void dohome()
  {
    std::cout << "看家" << std::endl;
  }
};
class Cat : public Animal
{
public:
  virtual void shout(){
    std::cout << "喵喵" << std::endl;
  }
  void dohome()
  {
    std::cout << "抓老鼠" << std::endl;
  }
};
int main(){
  Animal* base = NULL;
  base = new Cat();
  base->shout();
  //将父类指针转成子类
  //此时转换失败,因为父类指针现在指向的对象是猫
  //转换失败返回空(NULL)
  Dog* dog = dynamic_cast<Dog*> (base);
  if (NULL!=dog)
  {
    dog->shout();
    dog->dohome();
  }
    //此时转换成功,成功将父类指针转换成子类指针
  Cat* cat = dynamic_cast<Cat*> (base);
  if (cat != NULL){
    cat->shout();
    cat->dohome();
  }
    system("pause");
  return 0;
}

13.3 const_cast

const_cast<目标类型>(标识符):目标类型只能是指针或者引用


#include<iostream>
class  A
{
public:
  int data;
};
int main{
  //类似于结构体的初始化方式
  const A a = { 200 };
  //编译失败:const_cast 目标类型只能是引用或者指针
  //A a1 = const_cast<A> (a);
  A& a2 = const_cast<A&> (a);
  a2.data = 100;
  std::cout << &a << "——" << a.data << std::endl;
  std::cout << &a2 << "——" << a2.data << std::endl;
  A* a3 = const_cast<A*> (&a2);
  a3->data = 100;
  std::cout << &a3 << "——" << a3->data << std::endl;
    system("pause");
  return 0;
}

扩展:取址符号的解释


#include<iostream>
//& 表示取址符号,取的是当前变量本身的内存地址
int a = 1;   // 变量a
int &c = a;  // 引用c
int *b = &a; // 指针b
int main()
{
  std::cout << &c << std::endl;  //输出的是c的存储地址也就是a的地址
  std::cout << *&c << std::endl; //*取这个地址的值,输出1
  std::cout << b << std::endl;   //输出指针指向的地址,即a的地址
  std::cout << &b << std::endl;  //取的是指针b本身的地址
  system("pause");
  return 0;
}

13.4 reinterpret_cast

reinterpret_cast<目标类型>(标识符)

数据的二进制重新解释,但是不改变其值

reinterpret_cast是四种强制转换中功能最强大的,它可以暴力完成两个完全无关类型的指针之间或指针和数之间的互转,比如用char类型指针指向double值。它对原始对象的位模式提供较低层次上的重新解释(即reinterpret),完全复制二进制比特位到目标对象,转换后的值与原始对象无关但比特位一致,前后无精度损失


指针和数之间的互转


#include<iostream>
int main(){
  double d = 12.1;
  char* p = reinterpret_cast<char*>(&d);
  std::cout << "*p的值:" << *p << std::endl;
  // 将d以二进制(位模式)方式解释为char,并赋给*p
  double* q = reinterpret_cast<double*>(p); 
  std::cout << "*q的值:" << *q << std::endl; // 12.1
}

无关类型指针之间的类型转换


#include<iostream>
class Person{
public:
  void run(){
    std::cout << "我在不停的奔跑" << std::endl;
  }
};
class Sky{
public:
  void laugh(){
    std::cout << "天空乌云密布,暴雨要来了" << std::endl;
  }
};
int main(){
  Person* p = new Person();
  p->run();
  Sky* sky = reinterpret_cast<Sky*> (p);
  sky->laugh();
  std::cout << &p << "——"  << std::endl;
  std::cout << &sky << "——" << std::endl;
    system("pause");
  return 0;
}

14 C++内存知识

14.1 内存区域划分

在C++中,程序在内存中的存储被分为五个区:


1、代码区(Text):只读,共享,静态分配,存放程序执行代码,也可能包含常量


2、全局区:编译时分配内存,程序结束后由系统释放,存放全局变量和静态变量


DATA: 也叫GVAR(global value),存放已初始化的非零全局变量

BSS:存放零值或未初始化的全局变量,在程序开始时被清零

3、栈区(stack):由编译器自动分配释放 ,存放函数参数值,局部变量值等,按内存地址由高到低方向生长,其空间大小由编译时确定,速度快,空间小


4、堆区(heap):由程序员分配释放,按内存地址由低到高方向生长,其空间大小由系统内存上限决定,速度慢,空间大


每个线程都会有自己的栈,但是堆空间是共用的,下图为C++内存区域划分图



14.2 列举分配空间场景

区域类型 列举场景
代码区 类的成员函数和全局函数
全局区 全局变量和静态变量
栈区 局部变量和函数参数值
堆区 调用malloc函数或new运算符重载申请空间

14.3 变量的生命周期


类型 内存划分 作用范围 生命周期
局部变量 定义局部变量所在的大括号内 定义的函数被调用时,在栈开辟空间,函数调用结束,被清除
全局变量 全局区 所有源文件 在编译期即分配好了内存空间,在程序运行结束时,被清除
静态局部变量 全局区 定义局部变量所在的大括号内 在编译期即分配好了内存空间,在程序运行结束时,被清除
静态全局变量 全局区 只能在定义的文件中调用 在编译期即分配好了内存空间,在程序运行结束时,被清除

14.4 名称空间的用途

避免变量或函数重命名,命名空间可以将多个变量和函数等包含在内,使其不会与命名空间外的任何变量和函数等发生重命名的冲突


namespace ride{
  void PrintNum(int x, int y){
  std::cout << x*y << std::endl;
  }
}//打印两数相乘的命名空间
namespace plus{
  void PrintNum(int x, int y){
  std::cout << x+y << std::endl;
  }
}//打印两数相加的命名空间
int main()
{
  ride::PrintNum(1, 4);
  plus::PrintNum(1, 4);
  system("pause");
  return 0;
}


14.5 运行Test函数

例一


void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}


结果:编译成功,运行失败


原因:因为调用GetMemory相当于值传递,str指针实际上并没有分配到内存空间,访问空指针会运行失败,因为在C++中内存编号0 ~255为系统占用内存,内存地址编号为0的空间不允许用户访问


详细过程:在函数地址入栈后,p作为形参,只是拷贝了一份str的地址,并为该地址申请了空间,但是str指向的地址仍然是NULL


解决方法一


void GetMemory(char **p)
{
    *p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str);
    strcpy(str, "hello world");
    printf(str);
}

解决方法二


void GetMemory(char* &p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}


例二


char *GetMemory(void)
{
    char p[] = "hello world";
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
}

结果:打印结果不确定


原因:调用函数返回值是局部变量的指针,局部变量在函数被调用结束后,在栈区的内存空间被释放。因此str在打印时,指针指向的是非法(不可控)的内存地址,即野指针,在C++中访问野指针的结果是不确定的


例三


void GetMemory2(char **p, int num)
{
    *p = (char *)malloc(num);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}

结果:正常打印hello


原因:地址传递,把str指针的地址当做实参传递给形参,并在GetMemory2中为该地址申请地址空间


例四

void Test(void)
{
    char *str = (char *) malloc(100);
    strcpy(str, “hello”);
    free(str);
    if(str != NULL)
    {
        strcpy(str, “world”);
        printf(str);
    }
}


结果:运行时中断,后打印world


原因:free只是释放了malloc所申请的内存,并没有改变指针的值,因此由于指针所指向的内存已经被释放,所以其它代码有机会改写其内容,相当于该指针指向了自己无法控制的地方,也称为野指针。为了避免操作野指针,在free后应将指针指向NULL


15 函数的使用

15.1 函数指针

函数指针:指向函数的指针,可通过指针访问函数,函数名作为实参使用时


作用:通过使用函数指针可以简化代码,在一定程度上提高代码可读性


#include<iostream>
int add(int a, int b){
  return a + b;
}
typedef int(*FucPoint)(int, int);
//使用函数指针作为形参
void myFun(int a, int b, FucPoint p){
  p(a, b);
}
int main(){
    //函数名作为入参时,C++会自动将其转换为函数指针
  myFun(2, 3, add);
  system("pause");
  return 0;
}



15.2 静态函数

静态函数:被static关键字修饰的函数,静态成员函数属于类,不创建对象也可调用。类成员函数与静态成员函数的区别在于普通成员函数有一个隐藏的调用参数(this)指针


应用场景:从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据是否该方法和实例化对象具有逻辑上的相关性,如果是就应该使用实例化对象,反之使用静态方法


15.3 编程实现冒泡

要求:


创建一个无序的数组,通过定义的排序算法函数sort排序并输出

排序算法函数sort使用排序规则回调函数(函数指针)作为参数传入

#include<iostream>
bool judge(int a, int b){
  return a>b?true:false;
}
typedef bool(*FucPoint)(int, int);
class SortUtil{
public:
  static int* sort(int arr[], int len, FucPoint p){
  for (int i = 0; i < len - 1; ++i)
  {
    for (int j = 0; j < len - 1 - i; ++j)
    {
    if (p(arr[j] , arr[j + 1]))
    {
      int temp = arr[j];
      arr[j] = arr[j + 1];
      arr[j + 1] = temp;
    }
    }
  }
  return arr;
  }
};
int main() {
  int arr[9] = { 4, 2, 8, 0, 5, 7, 1, 3, 9 };
  int len = sizeof(arr) / sizeof(int);
  SortUtil::sort(arr,len,judge);
  system("pause");
  return 0;
}


相关文章
|
7月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
11月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
1182 98
|
11月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
1070 172
|
7月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
188 0
|
7月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
7月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
11月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
182 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
10月前
|
消息中间件 存储 安全
|
12月前
|
存储 算法 C++
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
文章详细探讨了C++中的泛型编程与STL技术,重点讲解了如何使用模板来创建通用的函数和类,以及模板在提高代码复用性和灵活性方面的作用。
169 2
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用