C/C++知识点零碎记录

记录本人日常比较难理解的知识点 不定时更新

每天读一遍

  1. vector就是特殊的数组,它的每个子单元都是特定的类型——==每个子单元左右是紧密连接的无空隙==

  2. 对一族类处理的情况,一般来说都不会往vector里面放对象本身,因为它们往往具有大小上的不同且vector只能保存同一类型;假设要把对象全部放进vector里,此时一般也只能放基类。这就会导致派生类超出基类的内容全部被截断——所以一般会往vector里面放类型一致的,大小一致的指针类型。而该类对象要么是在函数内声明创建的(也就是存储在栈上)要么就是通过new或malloc等创建的在堆上创建的对象。

  3. 为了便于批量处理,会把一族变量的指针放在同一个vector里面。因为派生类往往所具有基类不具有的内容,所以往往使用的是基类指针,即在vector里面保存的是基类指针。此时,我们可以通过基类指针调用本族类的通用方法,这件事情没有什么异议。而我们还需要通过基类指针调用同名但在基类和派生类中实现不同的方法,此时我们就需要用虚拟函数,也就是——

    基本应用场景:一个类的族(一个基类和其他派生类,所有的类都包含在这个族里),在C++里可以用一个vector存放不同类的指针,但是存进去的适合需要同一种类型的指针(强制转换),在此其间都会普遍采用存放基类的指针,因为每一个类都包含这一部分,将它们放在vector的主要原因是,它们有同一个基类,可以进行同样的处理。也就是,可以调用基类就存在的函数,比如说一个动物类,vector存的是动物园的所有动物的指针,现在模拟饲养员对每种动物的喂食、环境清理。像环境清理可以采用同一个基类函数,但是喂养的话,不同的动物需要不同的食物。所以需要定义分别定义不同的喂养函数。为了便于批量处理,即统一管理,C++新增了虚拟函数。这种函数的特性是,其基类写了这个函数给了这个函数定义,其派生类也可以给这个函数自己的定义,基类和派生类有各自自己的实现。

    在C++里有一个共同的前提,即派生类可以截断成基类,也就是前面所说的,把各动物指针放在vector里,在解引用这种指针后,将获得一般性vector的内容。此时,若调用一般性的函数,也就会直接调用基类自己调用的函数。而如果调用虚拟函数,它就会调用这个指针它的实际类型重新定义的虚拟函数,前者对应打扫卫生,后者对应喂食。

  4. 由于vector里存的指针所指向的对象存储在栈上时,会有必须给每个对象赋一个变量名的麻烦情况,因此往往才有在堆上创建对象的方式。所以,在程序结束时,需要将在这些堆上的内存释放掉。

  5. 一般来说,在创建这些对象时,会比较小心一些。任意使用,直到函数结束。也就是我们不会太关注堆上对象的释放问题,进而造成内存泄漏。因此可以采用封装的思想,将内存的申请和释放封装到新的类里——该思想在链表中已经反复强调过。因此,可以将vector里的元素从指针丰富为一个类,该类包含一个指针即具有原来的功能,还具有一个构造函数和一个析构函数,构造函数负责在堆上申请内存,析构函数负责释放内存。这种类称为句柄类

new和delete

处理都是堆上的内存

在进行new的时候发生的步骤:

  1. 根据new的目标的类型确定申请多大的内存
  2. 申请内存
  3. 在这块内存上调用目标类型的构造函数
  4. 构造函数在这块内存上按自定义或合成的定义分配这块内存
  5. 返回该内存的指针

虚拟函数

基本应用场景:一个类的族(一个基类和其他派生类,所有的类都包含在这个族里),在C++里可以用一个vector存放不同类的指针,但是存进去的适合需要同一种类型的指针(强制转换),在此其间都会普遍采用存放基类的指针,因为每一个类都包含这一部分,将它们放在vector的主要原因是,它们有同一个基类,可以进行同样的处理。也就是,可以调用基类就存在的函数,比如说一个动物类,vector存的是动物园的所有动物的指针,现在模拟饲养员对每种动物的喂食、环境清理。像环境清理可以采用同一个基类函数,但是喂养的话,不同的动物需要不同的食物。所以需要定义分别定义不同的喂养函数。为了便于批量处理,即统一管理,C++新增了虚拟函数。这种函数的特性是,其基类写了这个函数给了这个函数定义,其派生类也可以给这个函数自己的定义,基类和派生类有各自自己的实现。

在C++里有一个共同的前提,即派生类可以截断成基类,也就是前面所说的,把各动物指针放在vector里,在解引用这种指针后,将获得一般性vector的内容。此时,若调用一般性的函数,也就会直接调用基类自己调用的函数。而如果调用虚拟函数,它就会调用这个指针它的实际类型重新定义的虚拟函数,前者对应打扫卫生,后者对应喂食。

变量

变量可以分为局部变量和全局变量

作用域:一般指大括号之内的区域;

局部变量:作用域只在它所处的最小的括号里面;

有一种例外情况叫静态变量static;使用变量时,编译器会从最内层的作用域内找,只要在内层作用域找到了就不再外层作用域找,因此当同时存在全局变量和局部变量a时,会优先使用局部变量a。这种情况在函数中也出现。

生存期:变量存在内存的时间 ,和作用域一致,除非受到static影响。一般变量的生存期就是在本作用域的开头到末尾之间。注意,变量只在声明之后才可见,也就是说如果同时声明了全局变量和局部变量a,在局部变量a声明之前可以正常使用全局变量a,声明之后则只能使用局部变量a,除非显示调用域作用符。

例子:

如果在函数内返回了一个局部变量这个变量可以是在函数内声明的也可以是函数的传参,但要求其不是引用传参。如果本函数的返回类型是引用,则若把该变量当作返回值返回,则调用函数处将获得一个错误的变量。

引用变量的关键是,它是一个变量。

左值引用

1
2
3
4
int a=3;
int&b=a;
//a和b一般都是左值类型,没有什么int&类型,只要是左值类型都可以理解为int&类型

声明一个引用变量,需要用初始化的形式给其绑定一个变量。新声明的引用变量名就是原变量的别名。使用两个变量名都可以指向同一个地址,当作一个普通变量用即可,只是它们的地址是指向同一块区域。

变量:任何一个有地址的变量,即 非临时变量都可以视为一个左值,在C++的视角下,也就相当于是一个左值引用变量。

引用变量的关键是,它是一个变量,使用该变量可以获得他的值也可以获得他的地址进行进一步利用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
int a=3;
int& sum(int d){
return d;
}//不能这样返回!!
//d是一个局部变量,具有生存期,函数调用完成后其地址会被销毁 也就是就算a是一个全局变量,但是传参传进来之后就变成了局部变量
变量只在声明之后才可见,也就是说如果同时声明了全局变量和局部变量a,在局部变量a声明之前可以正常使用全局变量a,声明之后则只能使用局部变量a,除非显示调用域作用符。

//而int& 是一个左值变量,需要有地址,需要有地址。函数调用完后,d的地址被销毁,那么也无法获取返回值

//而我们之前经常用的return a+b,返回的是临时变量,是一个值,而不是地址。
int main(void){
int b=4;
sum(a);
return 0;
}

下面进行一个对比:

1
2
3
4
5
6
int a=5;
int & sum(void){
return a;
}
//编译器会从最内层的作用域内找,只要在内层作用域找到了就不再外层作用域找
//此时 a是一个全局变量,调用函数后不会被销毁,因此返回的是一个变量

类就是自定义类型,相较于内置类型而言,没有任何运算符。为了使类类型在使用时和内置类型一样顺畅。所以需要重载运算符,而重载运算符的方式就是写相应的函数。

CDate temp = *this;

这句话可以相当于把CDate 看成int

即 int tmp=*this

是一个初始化的过程。

CDate 是一个类类型 ,temp是CDate的一个实例对象。

this 是一个指向当前对象的指针,我们可以通过 this 来访问当前对象的所有成员

*i++

  1. 解引用

    ‘‘* i ’ 要求i是指针类型,* i利用的是i的值,和i是左值右值无关,但是*i的结果常是右值,常常再返回引用类型的函数时作为返回值。

    解引用的值可以作为左值也可以作为右值

    作为左值的时候,用存储的地址

    作为右值的时候,用存储地址上的值

  2. 指针

指针一个变量,变量有自己的值和地址,在函数传参时候,正常来说只能传值,但是在c++里新增了引用变量,所以在逻辑上,可以同时传值和对应的地址;

对于指针而言最重要的是它的值,即存放的地址。通过这个值可以改变另外一个内容。

  1. i++ 和++i

i++返回 的是加之前的值 是拷贝后的值 只能用作右值使用

++i 返回的是加之后的值

1
2
3
4
*out++= *begin++;
相当于
out=begin;
++out;++begin;

指针的数组 数组的指针

1
2
3
4
5
6
int p[]={1,2,3,4};
//&p 这个表达的意思是指向数组的指针
int p1[2][3]={};
int *q[3]=p1;
//q 也是指向数组的指针
注意&p 和*q 的区别

表达式返回值

就是类istream提供了一个转换来将cin转换成一个可以在条件中使用的值。我们还不知道这个值的类型,但是我们知道这个值可以被转换成布尔值,这个转换所产生的值取决于istream对象的内部状态,该状态会记住最近一次读数据的尝试是否成功。因此,用cin来作为条件等价于检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1+1;//此表达式具有返回值2
1;//此表达式具有返回值1

int a ;
a;//此表达式具有返回值 左值类型 a
a=1+1;//返回类型是左值类型a

int sum(int a ,int b){return a+b;}
sum(1,1);//返回类型int

cout;//返回类型 ostream&,左值类型
cout << 1 << 2;

/*执行的顺序 (cout << 1) << 2;
1.第一句返回类型为ostream&,第2句返回类型为ostream&
2.第二句话执行时实际上执行了 cout<<2
*/

if(cin>>x)
/*
实现过程为:
1.cin>>x 返回类型为istream&
2.实际执行为 if(cin)
3.这里将cin强制转换成了bool类型 是自定义的 利用了运算符重载
4.cin转换成了bool 他的返回值只跟cin内部定义的bool类型转换有关,返回值只与输入区是否还有内容有关 如果有内容则返回true 没有 返回false;和 scanf不一样
*/

代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
* @Description: 返回this
* @Author: ~光~~
* @Date: 2024-08-09 16:17:31
* @LastEditTime: 2024-08-13 11:40:04
* @LastEditors:
*/
#include<iostream>
#include<string>
#include<vector>
using namespace std;

struct Aclass{
int num;
Aclass &in(int b){
num = b;
return *this;
}
Aclass &add(const Aclass b){
num += b.num;
return *this;
}
operator bool(){
if(num != 0) return true;
return false;
}

};

Aclass &operator<<(Aclass &a,int b){
a.num = b;
printf("\na获取%d\n",b);
return a;
}


ostream &operator<<(ostream &out,Aclass &a){
out << a.num;
return out;
}

int main(void){
Aclass a,b;
a << 1 << 2;
if(a<<0){printf("这个人很无语");}
b.num = 2;
cout << a.add(b)<<endl;
(cout << 1) << "123";
cout << 1;cout << 2;

while(getchar());

return 0;
}