对象:
基本原则:里面有数据受保护,外面有访问数据的操作,不能直接接触到数据。 接收者自己决定做不做某个动作(然是如果他根本没有提供这样的服务呢?要直接去操作他的数据吗?) Class: 事物归纳总结成一个类别,东西的总类,相似的东西起个名字,是一个概念 Object:东西,实体
1、理解接口:类比灯座 和灯泡。
2、:: 两个冒号是做域的解析符,解析符后面的内容属于解析符前面。解析符前面什么也没有就代表后面的东西是全局的。
3、include做的只是简单的文本插入。
4、C/C++文件的编译过程:
- 首先是预编译,处理
# 开头的语句,生成 ‘ .i ’文件(在MAC里面是‘ .ii ’) - 然后是编译,就是把代码转化成 汇编语言,生成‘ .s ’文件
- 汇编,把 汇编语言 转化成 机器语言,是二进制的,能被计算机直接识别和执行,生成‘ .o ’文件
- 链接,把多个‘ .o ’文件链接成一个可执行文件
5、声明可以重复出现,定义肯定就不能了。所以,include的文件里只能有声明,不能有定义。
6、
构造函数:对象创建时就被自动调用 析构函数:对象删除时自动调用,无参数 两个函数都不需要人为写出来,但最好还是自己写一下
7、
在进入一个大括号之前,整个代码块所需要的存储空间就已经被分配好了,但里面创建的对象并没有构造好,程序执行到那里的时候才会调用相应的构造函数。
8、
当人为提供了有参构造函数时,编译器就不再提供默认构造函数了,此时你定义对象不加参数时,就会报错,你需要添加一个无参构造函数
9、
new要做三件事,一给变量分配内存空间,二如果是new了一个对象,则还要去调用构造函数,三返回变量的地址。 同理,delete先调用析构函数,再删除空间。
10、
普通的C++程序,操作系统会给C++一段内存空间用于new的时候,C++自己去分配内存
11、delete
delete只能删除new申请的空间
12、new完了记得delete,养成好习惯
13、private
private是对类来说的,不是对对象来说的,不同对象之间可以访问彼此的私有变量。 C++只在编译阶段对变量的访问权限(如private)进行检查。
14、friend
friend生命的函数、其他类可以访问类中的私有变量。
15、initialization list初始化列表
这个是真正的初始化,可以初始化任何类型的数据,包括类。他要早于构造函数执行,他们做完了,然后再做构造函数里面的事情。(对于int 和float类型的变量使不使用初始化列表没太大区别,但如果类的成员是一个对象 就会有不同。)
实际上构造函数中执行的操作得叫赋值,而并非初始化。在进行构造函数里的赋值操作时,如果没有先初始化,则会先进行初始化,如果成员变量是一个对象,就会寻找其默认构造函数,如果没有默认构造函数,就会报错。
16、小数
- C++编译器看到小数都会默认他是一个double类型,然后如果前面变量类型是float的话,会把双精度转换成单精度,这样会多做一步转换, 所以通常会在float类型的变量后面加一个f,来告知编译器这是一个单精度
- 默认情况,输出一个小数,最多只能输出6位有效数字
17、指针与const
- 常量指针(const int * p):指针指向的地址可以改,指针指向的值不可以改
- 指针常量(int * const p):指针指向的地址不可以改,指针指向的值可以改
- const int * const p:都不可以改
18、
传入函数的实参可以不是const修饰的类型,然后在函数体中对参数用const修饰
19、内存空间
全局区: 全局区的普遍规律:在全局区中,变量和常量之间有一定间隔,虽然他们都在全局区中
#include<iostream>
using namespace std;
int g_a = 0;
int g_b = 0;
const int g_c_a = 0;
const int g_c_b = 0;
int main()
{
int l_a = 0;
int l_b = 0;
static int s_a = 0;
static int s_b = 0;
static const int s_c_a = 0;
static const int s_c_b = 0;
const int l_c_a = 0;
const int l_c_b = 0;
cout << "局部变量 l_a 的地址为:" << (int)&l_a << endl;
cout << "局部变量 l_b 的地址为:" << (int)&l_b << endl;
cout << '\n';
cout << "局部常量 l_c_a 的地址为:" << (int)&l_c_a << endl;
cout << "局部常量 l_c_b 的地址为:" << (int)&l_c_b << endl;
cout << '\n';
cout << "全局变量 g_a 的地址为:" << (int)&g_a << endl;
cout << "全局变量 g_b 的地址为:" << (int)&g_b << endl;
cout << '\n';
cout << "全局常量 g_c_a 的地址为:" << (int)&g_c_a << endl;
cout << "全局常量 g_c_b 的地址为:" << (int)&g_c_b << endl;
cout << '\n';
cout << "静态变量 s_a 的地址为:" << (int)&s_a << endl;
cout << "静态变量 s_b 的地址为:" << (int)&s_b << endl;
cout << '\n';
cout << "静态常量 s_c_a 的地址为:" << (int)&s_c_a << endl;
cout << "静态常量 s_c_b 的地址为:" << (int)&s_c_b << endl;
cout << '\n';
cout << "字符串常量A的地址为:" << (int)&"hello world" << endl;
cout << "字符串常量B的地址为:" << (int)&"hello XuLei" << endl;
cout << '\n';
return 0;
}
栈区: 对于栈区中的变量,不能返回其地址。
#include<iostream>
using namespace std;
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
cout << *p << endl;
cout << *p << endl;
return 0;
}
第一次能够顺利打印是因为编译器做了保留,但这种保留不会一直持续。所以第二次打出了乱码。
20、引用(int&):与一个对象死死绑定在一起
给变量起别名,不会开辟新的内存空间,仍然是原来的那片内存
- 引用必须是合法的内存空间(堆区、栈区),不能是常量,不过常量引用可以这样
- 一旦引用就不能更改(一个别名只能对应一个变量,不能改)
- 函数中,引用传递可以达到和地址传递相同的效果
- 如果函数的返回值是引用,那这个函数调用可以作为左值(不过前提是返回的引用不是局部变量的引用)
- 引用的本质是指针常量
int a = 10;
int& ref = a;
ref = 100;
#include<iostream>
using namespace std;
void show_value(const int& x)
{
cout << x << endl;
}
int main()
{
const int& ref = 10;
cout << ref << endl;
int a = 10;
show_value(a);
return 0;
}
21、struct和class的唯一区别
struct的默认权限为公共 class默认权限为私有
22、常函数
- 在小括号的后面加一个 空格 和 const。
- 常函数里面的任何成员属性都是不能修改的,非要修改的话,需要在声明成员变量的时候前面加上
mutable 关键字。 - 常函数的本质是用const修饰this指针,让this指针指向的值也不可以修改(this指针本就是一个指针常量,指向不能改,但指向的值可以改)。
23、常对象
- 声明对象时前面加上
const ,常对象的所有属性都不能修改(除非属性声明时加了mutable ) - 常对象只能调用常函数(因为普通成员函数可以修改成员变量)
24、链式编程思想
类似于这种可以往后无限追加的操作:
cout << "A的值为:" << a << "B的值为:" << b << endl;
就像这种可以无限接左移符号
用函数实现这种编程的时候,返回值不能是void,因为如果返回void则无法继续追加,具体返回什么要视情况而定,下面以重载左移运算符为例:
#include<iostream>
#include<string>
using namespace std;
class person
{
public:
int m_A = 10;
int m_B = 10;
};
ostream& operator<<(ostream& cout, person& p)
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout;
}
int main()
{
person p1;
cout << p1 << '\n' << "hello world" << endl;
return 0;
}
25、负数取余
26、带符号和无符号
unsigned char c1 = -1;
signed char c2 = 256;
混合使用带符号和无符号类型会出问题:
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl;
std::cout << u + i << std::endl;
说明:
- 如果int占32位,则模为2^32=4294967296
- -42转化成无符号类型: - 42 + 2^32 = 4294967254
- 如果int类型的本来就是个正数,则直接计算,不用转化,哪怕算出来是个负数,再转化成无符号类型
27、初始化 ≠ 赋值
28、关于用const 修饰 变量、指针、引用
首先要学会读懂一个复杂的变量声明。用下图所示方法:
const int * const p;
现在我们用上图方法分析这条语句。从右往左读:
- 最先修饰p这个变量的是
const ,所以p的值本身不能改变。 - 然后是
* ,这个* 修饰p,表示p是一个指针。至此p是一个指向不能改变的指针,也就是说p这个指针的内存空间里只能存放一个固定的地址(这也意味着 在声明p的时候必须要对其进行初始化)。所以说上面这条语句其实是有语法错误的哈哈哈。 - 再往左看,是
int ,表明p是一个指向int 型的指针。 - 再往左又是一个
const ,这个const 修饰int ,表示指针p指向的int 型变量是个常量,不能改变。注意:这里的“不能改变” 指的是不能通过指针p来改变,我们可以通过不使用p的方法改变p指向的int 型变量。
看下面这个例子: 你看,上面这个例子中,虽然不能通过p修改a,但是我们可以直接用a修改a。
说到底,一个变量能否修改 还要看这个变量本身在声明时有没有加const。因此也有了下面这一条。
所以在执行赋值操作的时候就要格外小心,就比如你不能进行以下操作: 这你就报错啦!!! p是一个普通指针,你既可以修改p的指向,也可以修改p指向的值。但是a是一个常量,是不能改的,权限发生冲突,所以报错。 这样就是没问题的啦,不管你p怎么const 怎么不能变,都是你p自己一厢情愿。
- 注:理解复杂的数组声明:
当数组声明出现小括号时,先由内向外,再从右向左 以下内容摘自《C++Primer》第103页。
29、getline函数得到的字符串不包含换行符
30、string类的输入运算符和getline函数分别是如何处理空白字符的
- 标准库string的输入运算符自动忽略字符串的开头的空白(包括空格符、换行符、制表符等),从第一个真正字符开始读起,直到遇到下一个空白为止
getline() 则能够读取空白,直到遇到换行符为止,此时换行符也被读取进来,但是并不存储在最后的字符串中
31、
如果for 循环中没有修改c的值则合法,如果修改了则不合法,c的类型是const char &
32、不同的下标运算符
string和vector的下标运算符及其 操作是类内定义的,而数组的下标运算符及其 操作是C++内置的,这两者在使用上是有区别的。 数组的下标可以是负数,而string和vector的下标必须是无符号类型。
33、vector互换器
vector<int> v1, v2;
v1.swap(v2);
应用:收缩内存。 详情参考本篇博客
34、reserve()预留内存空间
如果vector中要存放的数据量较大,最好利用reserve() 成员预留空间。从而减少内存重新分配次数。
35、列表初始化要注意
对于列表初始化:
int k = { 3.14 };
36、非必须不使用后置递增递减运算符
37、求值顺序
C++中只有4中运算符明确规定了求值顺序:
求值顺序与 优先级和结合律 无关 特别的关于位运算: 此外,若移位的位数超过结果的位数,则也会产生未定义行为。
练习:
( a ):(*ptr != 0) && (*ptr++) 判断ptr指针指向的int值是否为0 ( b ):判断ival和ival+1两个值是否都非0 ( c ):没有规定<= 符号左右的求值顺序,产生未定义行为
38、前置后置递增递减运算符优先级 大于 解引用运算符
(a):合法。先执行++,也就是迭代器前移,并返回前移前的迭代器,然后对其解引用 (b):不合法。先解引用,得到string 类型,然后对string 类型++,没有任何意义 (c):不合法。先点运算符取empty() 成员,迭代器类型没有任何成员。 (d):合法。 (e):不合法。先解引用,得到string,再++,没有意义。 (f):合法。先++,并返回++前的迭代器,再解引用并取其成员。
39、sizeof并不实际计算其运算对象的值
在sizeof 中解引用一个无效的指针仍然是一种安全的行为,因为指针实际上并未被真正使用。 sizeof 不需要真的解引用指针也能知道她所指对象的类型。 所以下面这段代码不会报错:
int* p = NULL;
sizeof p;
sizeof *p;
40、算术类型的隐式转换
int a = 3.14 + 3;
算术类型之间的隐式转换被设计得尽可能避免损失精度。若表达式中既有整形又有浮点型,整形会转化成浮点型,上面例子中3转化成浮点型,然后执行浮点数加法。但最终在初始化时,被初始化的对象类型无法改变,所以浮点型又被转化成整形。
41、显式类型转换
|