C++ 随笔(二)

c++ 重要知识点

1.const修饰指针

  1. const修饰指针 --- 常量指针

  2. const修饰常量 --- 指针常量

  3. const即修饰指针,又修饰常量

 /*---常量指针---*/
 //const放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。
 //常量指针--指的是数值不能通过这个指针来改变。但是本身是可以改变的
 int a = 10;
 const int * p1 = &a; 
 a = 20;
 *p = 30;    //报错 
 /*---指针常量---*/
 //const修饰的是常量,指针指向不可以改,指针指向的值可以更改
 //const放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改
 int * const p2 = &a;
 //p2 = &b; //错误
 *p2 = 100; //正确

const 与 # define的区别

(1).类型

const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查;而宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误。

(2).编译器处理不同

宏定义是一个"编译时"概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;const常量是一个"运行时"概念,在程序运行使用,类似于一个只读行数据

(3).存储方式不同

宏定义是直接替换,不会分配内存,存储于程序的代码段中;const常量需要进行内存分配,存储于程序的数据段中。

2. void类型

void通常表示无值,但将void作为指针的类型时,它却表示不确定的类型。这种void型指针是一种通用型指针,也就是说任何类型的指针值都可以赋给void类型的指针变量。指针有两个属性:指向变量或对象的地址和长度。

(1).void指针是一种特别的指针

 // 说它特别是因为它没有类型 
 // 或者说这个类型不能判断出指向对象的长度 
 void *vp

(2).任何指针都可以赋值给void指针

 type *p;
 vp=p;
 //不需转换
 //只获得变量/对象地址而不获得大小

(3).void指针赋值给其他类型的指针时都要进行转换

 type *p=(type*)vp;
 //转换类型也就是获得指向变量/对象大小

(4).void指针不能解引用

 *vp   //错误 
 //因为void指针只知道,指向变量/对象的起始地址 
 // 而不知道指向变量/对象的大小(占几个字节)所以无法正确引用 

(5).5.void指针不能参与指针运算,除非进行转换

 (type*)vp++; 
 // vp==vp+sizeof(type)

void * 和 void 在函数返回值中的区别

在函数的返回值中, void 是没有任何返回值, 而 void * 是返回任意类型的值的指针.

#include <stdlib.h>
 #include <stdio.h>
 void voidc(int a); 
 void* voidcp(int *a);
 int main(){
     int a=10;
     int  *ap;
     voidc(a);
     ap = voidcp(&a);
     printf("%d\n",*ap);
 ​
     return 0;
 }
 void voidc(int a){ 
     printf("%d\n",a);
     return;    // 没有返回值
 }
 void* voidcp(int *a){
     printf("%d\n", *a);
     return a;  // 返回 int *
 }

3. 内存分区模型

五大内存分区分别是:栈区,堆区,BSS区(全局静态区),数据区(常量区),代码区

(1).栈区

  • 栈区的内存空间由系统管理 --> 即方法调用开始时开辟空间,方法调用结束时回收空间。

  • 栈区是从高地址低地址扩展,是一块连续的内存区域,遵循先进后出,后进先出(FILO)原则,使用效率高。

  • 栈区的内存空间是在运行时由系统进行分配。

  • 哪些变量是栈区的?例如方法的入参,内部定义的局部变量等,都存放在栈区

(2).堆区

  • 最大的特点 --> 空间大,需程序员自己手动管理。

  • 堆区是从低地址高地址扩展,与栈区相反,遵循先进先出(FIFO)的原则。

  • 堆区由系统api开辟空间(c/c++ --> malloc、calloc、realloc, oc --> alloc new),这个空间可以是不连续的,以链表结构存在,

  • 开辟出的空间的首地址是在栈区,例如LGPerson *person = [[LGPerson alloc] init];这个person指向所指向的地址是在栈区

  • 内存回收 --> free回收,做了两件事,一是释放堆区的内存,二是将栈区的指针置为nil

(3).BSS区(全局静态区)

  • 存放全局变量静态变量

  • 内存空间也是由系统管理 -->程序启动开辟程序结束回收,程序执行期间一直存在

  • static修饰的变量仅执行一次,生命周期为整个程序运行期

(4).data区(数据区/数据段)

  • 存放常量(整型、字符型,浮点型,字符串等),整个程序运行期不能被改变

    • 已初始化全局变量

  • 已初始化静态变量

  • 空间由系统管理,生命周期为整个程序运行期

(5).代码区(代码段)

  • 存放cpu执行的机器指令,代码区是可共享,并且是只读的。

内存泄漏(memory leak):是指申请的内存空间使用完毕之后未回收。一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序crash。(因此,开发中我们要尽量避免内存泄漏的出现)

内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用。

说明:

BSS段、数据段、代码段是在编译时候分配,堆栈和堆积是运行时候分配。

C++中利用==new==操作符在堆区开辟数据,堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符==delete==

4.引用

int a = 10;
 int& ref = a;  //等价于 ==》 int* const ref = &a;
 //返回局部变量引用
 int& test01() {
     
     int a = 10; //局部变量
     return a;
 }
 ​
 //返回静态变量引用
 int& test02() {
     static int a = 20;  //静态的局部变量存放在全局区,程序结束以后才会被释放。
     return a;
 }
 ​
 int main() {
 ​
     //不能返回局部变量的引用
     //在windows环境下的编译器可以这样做
     //但在liunx环境下不可以,原因如下
     /*
     * 函数的返回值为函数内部定义变量的引用,但函数在调用完毕后,函数内部定义的变量空间被释放,无法访问,从而造成的错误。
     * 当 int a = 10; 时,是局部变量。函数调用结束,局部变量会被销毁。无法返回变量的引用地址。
     * 所以需要加上static修饰
     */
     int& ref = test01();  
     //cout << "ref = " << ref << endl;  //第一次结果正确,是因为编译器做了保留
     //cout << "ref = " << ref << endl;  //第二次就是错误的,因为a的内存已经被释放了
 ​
     //如果函数做左值,那么必须返回引用
     int& ref2 = test02();
     cout << "ref2 = " << ref2 << endl;
     cout << "ref2 = " << ref2 << endl;
 ​
     test02() = 1000;  //相当与tase02()和变量a操作的是同一个内存
 ​
     cout << "ref2 = " << ref2 << endl;
     cout << "ref2 = " << ref2 << endl;
 ​
     system("pause");
 ​
     return 0;
 }

左值引用和右值引用:(c++11的新特性)

有一个很好区分左值和右值的方式,就是是否可以对表达式取地址。可以获取地址的表达式就是左值,且持久性变量都是左值,反之则是右值。

 int i = 42; //i是左值,可以对i取地址
 int &r = i; //r是左值引用,绑定左值i
 ​
 int &&rr2 = i * 42; //不能对表达值取地址,所以表达式结果是右值,可以绑定到右值引用
 int &&rr1 = 42; //不能对字面常量取地址,所以字面常量是右值,可以绑定到右值引用
 ​
 左值通常是变量,数组元素,结构体成员,应用。
 右值通常是常量,常量表达式。
 ​
 const int &r2 = i * 42; //不能对表达值取地址,所以表达式结果是右值,可以绑定到const左值引用

5.c++创建对象的两种方式

  • 不通过“new”关键字,在栈上创建。在栈中的对象,其作用范围只是在函数内部,函数执行完成后就会调用析构函数,删除该对象使用 “.” 而不是 “->” 调用对象的方法。

  • 通过关键字“new”,在堆上创建。在堆中的对象,必须要程序员手动的去管理该对象的内存空间 。用运算符“->”调用对象的方法。new出来的对象必须用指针指明地址。

 A a;  // a存在栈上
 A* a = new a();  // a存在堆中

1 前者在栈中分配内存,后者在堆中分配内存

2 动态内存分配会使对象的可控性增强

3 大程序用new,小程序不加new,直接申请

4 new必须delete删除,不用new系统会自动回收内存

6.拷贝构造函数

拷贝构造函数是一种特殊的构造函数,其形参是本类对象的引用。拷贝构造函数的作用是在建立一个新对象时,使用一个已存在的对象去初始化这个新对象。每个类都必须有一个拷贝构造函数。可以自己定义拷贝构造函数,用于按照需要初始化新对象;如果没有定义类的拷贝构造函数,系统就会自动生成一个默认拷贝构造函数,用于复制出与数据成员值完全相同的新对象。

 class Score{
 public:
     Score(int m, int f);  //构造函数
     Score();
     Score(const Score &p);  //声明拷贝构造函数
     ~Score();               //析构函数
     void setScore(int m, int f);
     void showScore();
 private:
     int mid_exam;
     int fin_exam;
 };
 ​
 Score::Score(int m, int f)
 {
     mid_exam = m;
     fin_exam = f;
 }
 //定义拷贝构造函数
 Score::Score(const Score &p)
 {
     mid_exam = p.mid_exam;
     fin_exam = p.fin_exam;
 }
 ​
 Score sc1(98, 87);
 Score sc2(sc1);    //调用拷贝构造函数
 Score sc3 = sc2;   //调用拷贝构造函数

调用拷贝构造函数的三种情况:

  • 当用类的一个对象去初始化该类的另一个对象时;

  • 当函数的形参是类的对象,调用函数进行形参和实参结合时;

  • 当函数的返回值是对象,函数执行完成返回调用者时

浅拷贝和深拷贝

浅拷贝就是由默认的拷贝构造函数所实现的数据成员逐一赋值。通常默认的拷贝构造函数是能够胜任此工作的,但若类中含有指针类型的数据,则这种按数据成员逐一赋值的方法会产生错误。

 class Student{
 public:
     Student(char *name1, float score1);
     ~Student();
 private:
     char *name;
     float score;
 };
 //如下语句会产生错误
 Student stu1("白", 89);
 Student stu2 = stu1;
上述错误是因为stu1和stu2所指的内存空间相同,在析构函数释放stu1所指的内存后,再释放stu2所指的内存会发生错误,因为此内存空间已被释放。解决方法就是重定义拷贝构造函数,为其变量重新生成内存空间。
Student::Student(const Student& stu){
     //申请新的内存空间
     name = new char[strlen(stu.name) + 1];
     if (name != 0) {
         strcpy(name, stu.name);
         score = stu.score;
     }
 }

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,依然会提供默认拷贝构造

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数(如果需要其他的构造函数就需要自己提供)系统提供的拷贝构造函数会将所有的属性值都拷贝一份。

7.函数重载

(给函数赋予另一种功能,以适应不同的数据类型)。

同一个函数名对应不同的函数实现,每一类实现对应着一个函数体,名字相同,功能相同,只是参数的类型或参数的个数不同。多个同名函数只是函数类型(函数返回值类型)不同时,它们不是重载函数

int add(int a,int b)
 {
     return a+b;
 }
 double add(double a,double b)
 {
     return a+b;
 }
 int add(int a,int b,int c)
 {
     return a+b+c;
 }
 //返回值的类型不同,不是函数的重载
 double add(int a,int b,int c)
 {
     return a+b+c;
 }

7.内联函数

在函数名前冠以关键字inline,该函数就被声明为内联函数。每当程序中出现对该函数的调用时,C++编译器使用函数体中的代码插入到调用该函数的语句之处,同时使用实参代替形参,以便在程序运行时不再进行函数调用。引入内联函数主要是为了消除调用函数时的系统开销,以提高运行速度。

说明:

  • 内联函数在第一次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码

  • 在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等

  • 使用内联函数是一种空间换时间的措施,若内联函数较长,较复杂且调用较为频繁时不建议使用

#include <iostream>
 using namespace std;
 ​
 inline double circle(double r)  //内联函数
 {
     double PI = 3.14;
     return PI * r * r;
 }
 ​
 int main() 
 {
     for (int i = 1; i <= 3; i++)
         cout << "r = " << i << " area = " << circle(i) << endl;
     return 0;
 }

8.C++ 类的三种访问权限

(1).公共权限 public 成员:类内可以访问,类外可以访问任何一种继承,子类可以访问父类的公共成员 (2).保护权限 protected 成员:类内可以访问,类外不可以访问任何一种继承,子类可以访问父类的保护成员 (3).私有权限 private 成员:类内可以访问,类外不可以访问。任何一种继承,子类不可以访问父类的私有成员

img

9. 虚函数表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值