C++ Primer Plus-基础部分-note1

txt文档转存
'***************
cppreference
微软C++语言文档
-exec disassemble /m main --vscode 中查看程序汇编代码
-exec info registers 得到寄存器的信息:

文件所在目录
gcc -S xxx.cpp -o xxx.s
发现左侧文件栏新出现一个xxx.s 汇编文件

int *p = new int[10]
delete [] p
array<int, 10> p
vector< string> a
unique_ptr< int> p(new int)
tmeplate < typename AnyType> //标明要建立一个模板
void Swap(AnyType &a, AnyType &b)
{

}

ostream & operator<<(ostream & os, const Time & t) cosnt
{
os << t,hours <<" haha" <<t.minutes;
return os; // 返回指向调用对象的引用
}

当然,当你需要关闭自动转换,这样做
explicit object(double lbs);//关闭隐式转换,但显式强制转换仍然ok
object = classNmae(19.6)//ok
object = (classNmae)19.6 //ok
object = 19.6 //ng

位操作,文件操作
书上跳过的
1.线程
2.volatile mutable
3.低级数值转换 C++17 -C++高级编程4th

编程方式
C++:
新术语:
(C语言代表的过程性语言-大任务分解成小任务函数-top-down)
代码重用
面向对象(C基础上添加的以类为代表的的OOP-bottom-up)–管理大型项目的工具–强调数据
对象,类;封装,继承(旧类派生新类),多态(为运算符和函数创建多个定义,通过上下文确定用哪个);数据隐藏(保护数据,避免非法访问)
泛型编程–模板STL–执行常见任务的工具–创建独立于类型的代码,应对多数数据类型

第二章

标准的C++结构
include < iostream>
/*
兼容C, 所以可以这样
include <stdio.h>
include < cmath> 等价于 inlcude <math.h>—c语言中
*/
int main()
{
using namesapce std;
cout << “hello world” << endl;
std::cout << “haha” << endl // std空间中的cout函数
cin.get() // 让黑框框等待
return 0;
}

类:用户定义的一种数据结构,需要给出数据类型和科执行的操作,相当于数据结构汇总的抽象数据类型
类之于对象,类型之于变量。对象是类的实际应用,即实体。

初始化器,防止类型转换错误?
{}-列表初始化,用于给复杂的数据类型提供值列表,不允许数据缩窄(narrowing),允许类型扩大.
也就是输入的数据必须小于等于定义的数据类型大小
int a, b = 1;—ok
int a = {1} int a{1} —ok
int a = {} int a{} 初始化为0

cout << hex;
cout << 12 << endl;
输出12的16进制表示
C++ 将整型默认存储为int,除非它太大或者人为规定后缀

char ch = 75
cout << ch +1 << endl; // 76

bool a true;
int ans = true; // 1
bool start = -100; // true
bool stop = 0; // false

(typename) value //C
typename (value) //C++
static_cast (value)

第四章 复合类型

数组

不指定数组大小,让编译器自己计算,对于int等类型来说很糟糕。但是对于字符串来说可以。因为
你输入的就是需求的
通常数组大小在定义时就要确定
部分初始化时,其余默认为0

int a[4] {1,2,3,4};–列表初始化–C++11 ,{不允许类型“降级”}// C语言的初始化方式也OK

vetor(容器)

https://blog.csdn.net/feikudai8460/article/details/119910264-初始化方法
–模板类 & array–数组的替代品–自由存储区或者堆
vetore使用了new和delete自动管理内存
< vector> (包含在std中) 效率低,功能强大
vector< typeName> vt(n_elem)
vector< int> vi //长度为0
vector< int> vi = {1,2,3.0,4,5,6,7};
vector< int> vd(n) n可以是整型常量,也可以是变量
vector< int> vd(n,3) n个3
vector< string> vd {5, “haha”} 也可以
vi vd 都是vector< int>的对象。
插入或添加值自动调整长度,所以初始化长度为0

定义完成后可以像数组一样使用
vd[0] = 1

array C++11

定义完成后可以像数组一样使用
模板类–长度固定的数组首选,使用栈–静态内存分配。
< array> 对象长度固定
array<typeName,n_elem> arr e.g. array<int,2> = {1,2};
n_elem 不能是变量
可以array1 = array2 直接赋值
数组只能一个一个赋值

C/C++不检查数组越界,只能自认倒霉。
对于vector和array 你可以和数组一样,也可以用成员函数at()来花时间检查 。如array<int,2> arr; arr.at(1) = 2 at()会将第一个空格前的捕获非法索引,并中断。

字符串

cin 使用空白,如空格、制表符和换行符来确定字符串的结束位置,cin读到输入字符串之间空格时候,会将第一个空格前的
读入,然后自动在结尾添加空字符。没读的会放在缓冲区

< iostrem>中getline类方法 cin是个对象
getline()使用回车键输入的换行符来确定结尾–cin.getline()
cin.getline(name,20)-- 两个参数,第一个存储输入的数组名,第二个是要读取的字符数。参数20,最多读取19,结尾需要添加空符

读取时,get() 类似getline()。但是它不会丢弃多余的字符,而是保留在输入队列,等待get()再次读入。
但是,换行符也会保留。所以,可以cin.get(arr,20)之后可以用cin.get()处理换行符,为下一行输入做准备。不然就卡在这儿了。
cin.get()可以用来处理不需要的输入(如回收换行符)。
另外,可以用get将两个类成员函数拼接起来, cin.get(arr,20).get()–因为cin.get(arr,20)返回cin对象,可以调用get
getline类似。
cin也会将回车生成的换行符留在输入队列,需要get()或者上方将cin.get()串联
getline简单,但是get排查错误简单。

get在读到空行后,会设置失效位,阻断接下来的输入。可以用cin.clear() 清除
大于设定长度时候,getline设置失效位,关闭后面的输入。

字符串字面量,以值的形式而不是变量的形式出现,会被放到内存的只读部分,这样多次使用其实都是在读取该区域内存块,成为字面量池 literal pooling。C++标准指出字符串字面量是n个const har的数组,但是实际使用中仍然可以将其赋值给不带const的char*,这样就可以修改器内容,具体情况就取决于编译器了。更安全的赋值是加上const,当然,可以将字符串字面量作为char[]的初始值,放到该数组而不是字面量池,这样就可以修改。

原始字符串 R"(…)"

不需要因为要输出 ” 转变成转义字符 “。 const char* str = R”( hello “”world" !)“; 同时多行输出,也只需要enter键到下一行即可,但格式上,\n分段等效的是下一行定格。同时原始字符串可使用扩展的原始字符串字面量输出 )”: const char* str = R" - (HAHA ) HAHA) - "; 其中 - 分隔符可以用其他没有出现在需要输出字符串中的符号代替,用于操纵数据库查询,正则表达式或者文件路径

string类

string类位于std中,必须用using编译命令,或者std::string引用。
string类隐藏字符串的数组性质,当做普通变量来处理。
string str = “haha”;
string str = {“haha”}
string str {“haha”}

通过应用string类c_str()可以返回c风格字符串的const字符串指针
C++14之前 data() 返回const char*, C++17 在非const字符上调用,返回char*

源代码中的字符串字面量通常被解释为cosnt char*,使用用户定义的标准字面量s可以解释为std::string,
auto str = “haha”; // const char*
auto str = "haha"s;// std::string

string类有将数值转换为字符串的辅助函数to_string,返回占用新内存的string对象.
string对象转换为数值,例: int stoi( const string& str, size_t *idx = 0, int base = 10)
idx是个指针,接收第一个未转换的字符的索引,base是进制 (e.g. stor(s, 0,10);

低级数值转换 C++17 -跳过

std::string_view C++17
在< string_view>中,是const string&的替代品,仅当用于函数参数(接收只读字符串)。没有c_str,有data()
添加了remove_prefix(size_t) ,remove_suffix(size_t)来向中间紧缩字符串。
https://zhuanlan.zhihu.com/p/581470892 string 与string_view 区别
C++17 之前接收只读字符串,要么string类调用c_str或data()获取const char* ,要么因使用const string&而产生包含字符串的对象副本。
string_view 相当于原字符串的只读视图,不产生新串,也不能修改原串。另外,原字符串命要更长,
string(string_view)显示转换为字符串对象
string +string_view.data() 这种类型的才可以通过编译, 无法直接连接string和string_view

确认项目如何表示字符串:

  1. 不应当使用C风格的字符串
  2. 可对自己框架中可用的字符串功能标准化
  3. 如果使用std::string, 应当使用std::string_view将只读字符串作为参数传递给函数,或使用类似的类

可以使用C-风格字符串来初始化string对象
可以cin输入,cout输出,可以使用数组表示法访问sting对象汇总的字符。

相对于数组,string类可以赋值“=”给另一个string类
可以使用“+”将两个string对象合并。
C风格可以使用< cstring>中strcpy,strcat,但是存在目标字符串数组太小的问题。string类可以自动调整大小。
C中strlen, c++ str.size()
string的 cin 每次读取一行,而不是一个单词

重点待解决:getline函数和getline方法
getline(cin,str) cin–从哪儿找输入, str–存储在哪儿。str为string类,长度自动变化。
cin.getline(str,20)–方法

其他类型的字符串字面值 C++ Primer Plus P87 省略

结构体struct

struct xx
{
}bb;
C-风格: struct xx a;
C++风格:xx a; 省略了struct关键字。在C中这么干得typedef struct aa{} xx;
结构体初始化,参数可以放在一行。 xx a= {“a”, “b”};
初始化列表赋值: xx a {“a”, “b”};
初始化大括号内为空,则每个字节初始化为0
e.g.
#include < string>
struct aa
{
std::string name; // 一定要定义在std空间内
}
结构体可以 “=” 直接赋值 同样不允许缩窄

共用体union 数据项多种格式不同时使用,可以用来节省空间
同时只能存储一个数据类型

枚举enum–另一种创建符号常量的方式,可以替代const
枚举量默认从0开始,考科一单独赋值
当使用枚举量时,直接使用0,1 等数字是错误的。枚举量可以相互加减但没意义。除非经过强制转换
e.g. enum bits{one = 1, four = 4}
enum bigstep{first, second = 10, third} 此时first默认0 third 默认101
同时,enum{zero,null= 0}成立

强制类型转换,将枚举类型转换到枚举取值范围,即使没有对应的枚举量,但也成立(范围确立P97)

指针

int* p; p = 123; error, p不知道指向哪儿. 所以,在指针解引用前一定要初始化为一个确定的,适当的地址,null不行。

new一个。啊哈
new代替malloc calloc 分配内存
过程:程序员告诉new要求哪种类型的内存,new找到一个合适的内存块,返回地址,程序员再将地址赋值给一个指针:
int* p = new int -->typeName * pointer_name = new typeName
变量存储于栈,new出来的存储于堆heap或者自由存储区
new返回0–空指针(较老的实现),表示没空间了。
delete释放new分配出来的内存
delete p;释放p指向的内存(针对上方,会删除*p 被赋予的值索在的内存,delete p前后, p内容不变),但是不会删除p本身,因此可以重定向到新内存块,重复使用。否则内存泄露memory leak

释放已经释放的内存块,结果未知。但是空指针随便搞

大型数据 可以用到new
int *arr = new int [10]; 返回第一个元素的地址 在运行时,按需创建,然后 arr可以当数组名
delete [] p; [] 指明是数组
new 也可创建结构

delete new配对
不要delete 同一块内存两次
new[]为数组分配内存, delete[]释放
new[]为一个实体分配内存,则直接delete释放
new出来的数组的名字不会被sizeof理解为数组,而只是理解为一个这个指针的长度
(C风格数组名被解释为第一个元素地址,对数组名取地址,得到的是整个数组的地址, 虽然输出的结果是一样的,但概念上,
int t[12];
cout << t << endl; // 首元素占用的4个字节的内存的地址 t+1是下一个元素
cout << &t << endl;//长度为12*4内存块的地址 &t+1是加了一个t数组

int {*t2) [12] = &t //数组指针 *t2 [12] 则 t2[12]结合,t2就是一个指针数组,20个指针在数组里

多数情况下 数组名,char指针及双引号括起来的字符串常量都被结识为字符串第一个字符的地址
因此 cout 指针,则打印其地址,但是cout char指针,则打印其指向的字符串,如果要显示字符串地址,则必须(int *)强制转换
C:
strcpy内存不够,则会覆盖后续内存, strncpy,设定内存不够,在不会添加到空字符,因此,需要手动为他加上空字符—真麻烦

存储类:
自动存储:函数内部定义的常规变量存储空间,自动变量,自动产生和消亡。局部变量。作用域为代码块 花括号内-----存储在栈
静态存储:整个程序执行期间都存在。一种是函数外定义,一种是static定义。
动态存储:new和delete管理的内存池—free store/heap,导致分配出来的最终不连续

第五、六章

内置类型,前后缀速度差多, 但是你写的类,前缀函数,值+1,然后返回, 但是后缀函数先复制副本,副本+1,再返回。 前缀块一丢丢

*++pt; //pt递增,再取值
++*pt;// 先取值pt,再递增,
(*pt)++
pt++;//后缀优先级更高,++作用于pt。而不是pt,但是解除引用是对原来的地址,这条语句执行完后,pt指向下一个

e.g.
double arr[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
for(double x : arr)
cout << x << std::endl;

for(int x : {1, 2})
    cout << x << std::endl;
    
for(double &x : arr) // &引用修改
    x = x * 0.1; 

cin
cin.get(ch) 在 < istream>中参数已经被声明为引用了。 修改内存中值的方式:引用或者传递地址进去
函数重载:cin.get() cin.get(ch) cin.get(arr,20)

EOF ctrl +d/z 键盘模拟输入文件结尾----哨兵字符
检测到EOF, cin将eofbit和failbit都设置为1, 使用eof()和fail()查看。此时cin.eof() 返回bool值 true.
同样 eofbit或者failbit 为 1 则 fail()返回true
eof() 和fail()方法报告最近读取的结果,事后诸葛亮。
C语言中判断结尾:(ch = getc(fp))!= EOF
C++中,cin.fail() == false //test for EOF
程序中, 可以cin,clear()清楚EOF标记,但是键盘ctrl+Z则没办法

检测输入
while(cin.fail() == fail)
while(cin.fail()) //cin.get(char) 返回cin
while(cin)//while input is successful 可以检测输入失败的其他原因

ch = cin.get() 类似getchar()–>返回字符编码的int值
cin.get(ch)返回一个对象

C中引入 <iso646.h> C++中不用
ze &&–and ||–or !–not

< cctype> 字符判断函数库

5 > 3 ? 10 : 20 – 10

switch(integer-expression) // 类似VBA select case
{
case lable1 : statement // 标签须为整数
break;
case lable2 : statement

default : statement
}
continue 跳过余下的diamante,开始新循环;break 跳出该循环
文件读写–需要的时候再看

第七章

函数原型中变量名不是必须的
可以将数组塞进结构体返回
ansi C 中 不指定参数,留待后续定义参数列表 则参数括号为空, C++ 为(…)
函数参数通常按值传递–传值传值,传的是值的副本

函数声明中的数组名 实际是个指针 且函数使用原本的数组,因为指针,所以不是副本
e.g.

int fill_array(double ar[], int limit)
{
    using namespace std;
    double temp;
    int i;
    for(i = 0; i < limit; i++)
    {
        cout << "Enter value #" << (i + 1) 《《 “:”;  
        cin >> temp;
        if(!cin) // 是否为有效输入
        {
            cin.clear(); // 回复输入
            while(cin.get() != '\n')
            {
                continue;
            }
            cout << "bad.\n";
            break;
        }
        else if(temp < 0)
            break;
        ar[i] = temp;
    }
    return i;
}

void swap_(int a[]) //这样就可以交换数组元素了--传递的数组参数实际传进去的是要给数组指针(int a*也可)
{
    int temp = 0;
    temp = a[0];
    a[0] = a[1];
    a[1] = temp;
}

尽可能使用const

  1. 可以避免由于无意间修改数据导致的错误
  2. 使用const使得函数可以处理const和非const实参,否则只能接受非const实参,因为非const的函数可能会修改const指向的位置,那const实参就没意义了。

如果条件允许,则应该将形参声明为指向const的指针,这样函数可以正确生成并使用临时变量

const的规则:const默认与其左边结合,当左边没有任何东西则与右边结合。或者可以尝试从右向左看表达式
const int p* int const *p都表示“不能通过这个指针去修改那个变量, 可改变指针本身所指向的地址”。这并不能使得那个变量成为const
int * const b,则是表示一旦得到了某个变量的地址不能再指向其他变量,可以通过指针改变其所指向的内容但只能指向该地址,const此时修饰指针

bottom-up programming,从组件到整体,适合OOP。强调对数据的表示和操纵。
top-dowm programming 强调模块化设计方案,然后研究细节。
最终的产品都是模块化程序

函数与二维数组,参数是一个指针 ☆
原型:
typeName func(int (*ar)[4] );//二维数组 n行4列
typeName func(int ar[] [4]);//二维数组
typeName func(int *ar );//可表示一维数组
typeName func(int ar[] );//可表示一维数组

函数与结构
1.按值传递整个结构
2.传递结构地址
3.按引用传递

函数与string & array
const std::array<std::string,Seasions> Snames = {“Spring”, “Summer”, “Fall”, “Winter”} //二维数组

函数指针–指向函数地址的指针—☆ MMP重难点
函数地址:即函数名(后面不跟参数)。think()–函数, think–函数地址
声明函数指针的结构类似声明一个函数,必须指出参数和返回值类型。
e.g.

double func(int) --> 函数指针声明:
double (*pf)(int)--> pf 函数指针 int参数 double返回值
pf = func;
double x = fun(1);

double y = (*pf)(2); //明示这是个函数指针
double z = pf(3);

第八章

1.C++内联函数–不能递归

普通函数执行过程:执行到函数时候,保存当前位置,并跳转到函数内存地址,并将函数参数复制到堆栈,执行。结果
保存到寄存器。之后再跳回断点处继续程序执行。
内联函数,将函数编译代码与其他程序代码内联(相当于把函数复制到其他代码汇总,取代调用),无需跳转执行函数。
速度更快,但是内存占用更高。
当执行函数的时间很短的时候,内联函数更好。但是除非经常调用该函数,不然节省的时间绝对值不大。

使用内联,函数声明前加上inline 或者 函数定义前加上inline
一般直接将函数定义放在声明的位置,从而省略声明,能一行定义完的函数最合适,多行的不太好
出现在声明位置的函数定义,整个被当做了函数原型。

内联的原始实现—C语言中的#define

2.引用变量

–已定义的变量的别名,主要用作函数的形参,这样函数将使用原始数据,而不是副本。起到了和指针类似的作用(引用与被引用的指向同一块内存)
与指针区别
1.表示方法不同
2.必须在声明引用时将其初始化,不能像指针那样,先声明再赋值。
3.引用更接近const指针,初始化后,无法更改。

引用传递参数,可以改动原始值。按值传递使用的是参数的副本。
fun(x+3.0)有时可以执行是因为C++自动创建了一个临时变量,并初始化为x+3.0.然后再使用临时变量
——如果实参与形参不匹配,C++将生成临时变量。参数为const引用时候,才可以这么干。
引用参数是const,创建临时变量情况:
1.实参类型正确,但不是左值; 形参为const double 引用, 但是传给它的实参却是7.0这个数
则编译器创建一个匿名变量并初始化为7.0,并让引用指向他
2.实参类型不正确,但可以转换正确;
double edge = 99; 这种情况类型不符合,但是可以转换
(devpp mingw9.2 c++ 14)

左值:是可以被引用的数据对象:变量,数组元素,结构成员,引用和解除引用的指针
	C中,可以在赋值语句左边的实体视为左值,C++中常规变量和const变量都可视为左值
	左值:可以通过地址访问
非左值:字面常量(引号括起来的字符串除外,他们可以用地址表示),包含多项的表达式。

尽可能使用const

  1. 可以避免由于无意间修改数据导致的错误

  2. 使用const使得函数可以处理const和非const实参,否则只能接受非const实参,因为非const的函数可能会修改const指向的位置,那const实参就没意义了。

  3. 如果条件允许,则应该将形参声明为指向const的指针,这样函数可以正确生成并使用临时变量 。

    将引用声明为const,C++必要时生成临时变量
    左值引用: & int rats; int & rodents = rats
    右值引用: &&–》 double && rref = std::sqrt(36,00); cout << rref;

返回引用 参数& a 则 return a返回引用
不要返回函数终止时候不再存在的内存单元引用。返回输入的参数或者new一块新内存,返回指针
const free_throws & cloe(free_throws & ft)
{
free_throws * pt; //创建无名结构,并pt指向
*pt = ft;
return *pt //返回结构的引用
} //隐藏了new ,需要配套delete

传统的返回值,函数先return复制到一个临时位置,

只要=好左边是可修改的左值,长啥样子都行
const用于引用返回类型–不想修改的时候☆

string类定义了一种char*到string的转换功能,这样可以使用C-风格的字符串初始化string对象
函数内部局部变量不能被外部引用,但是可以被传出给C++自动创建的临时变量。之后消失

继承:将特性从一个类传递到另一个类 基类 派生类
基类引用可以指向派生类,无需强制类型转换
——这样可以创建一个可以接收基类对象的函数,调用时候也可以将派生类作为参数。

引用的使用
P291 8.2.7

3.默认参数

不同个数的参数列表可以调用同一个函数
省略了实参的时候。自动使用的值
编译器通过原型了解函数的参数数目
e.g. char * left(const char * str, int n = 1); 将值赋给原型中的参数 就行
对待参数列表的函数, 必须从右向左添加参数默认值,不能跳过,而提供实参赋值形参时候,会从实参列表左往右,不能跳过

4.函数重载–函数多态。使用多个同名函数

通过上下文确定要使用的重载版本
两个函数的参数类型,数目,位置相同–特征标相同–参数列表相同,变量名无所谓,返回值类型也无所谓
C++允许定义名称相同的函数,条件是参数列表不同。这样才可以重载。

其实也就是函数名一样,其他乱七八糟的不一样,尤其是参数列表不一样。然后编译器根据参数列表自动选择合适的
函数实体运行,就是所谓的函数重载。

使用场景:执行相同的任务,但是使用不同类型的数据
C++编译器通过名称修饰(name decoration)名称矫正(name mangling,),根据形参类型函数名称转换为加密的内部表示–按一定规则编码以区分–不同编译器不同约定规则
(B站-mq白cpp-C++函数返回类型不一致却能成功链接上-BV1DA4m1c7wW)。

5.函数模板

使用泛型(无具体数据类型)来定义函数–而不是具体数据类型–通用编程,
实例化时,将类型作为参数的传递给模板创建该类型函数
实际上市对函数重载的进一步抽象

模板创建:
tmeplate < typename AnyType> //标明要建立一个模板
void Swap(AnyType &a, AnyType &b)
{

}
typename 可以用class替代,作用一样
模板知识告诉编译器如何定义函数,不创建具体函数。
同一种算法用于不同类型的数据时候,请使用模板
使用方法:
//声明
tmeplate < typename AnyType>
void Swap(AnyType &a, AnyType &b)
int main()
{
}
//实体
tmeplate < typename AnyType>
void Swap(AnyType &a, AnyType &b)
{
}
当传递进实参,编译器自动创建合符合实参类型的函数,程序员看不到生成的函数
模板通常放在头文件中

模板重载

–并非所有类型使用相同的算法,可以同名模板不同参数列表实现不同算法。模板重载的函数特征标必须不同。并非所有的模板参数都是模板参数类型。具体类型也可以。

方法:
对于给定的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及他们的重载版本
显示具体化的原型和定义以 template<> 打头,并通过名称来指出类型
而非模板函数优先于具体化和常规模板, 具体化优先于常规模板,
e.g.
//非模板函数 -1
void swap(job &, job &);
//模板 --3
template < typename T>
void swap(T &, T &);
//显式具体化模板函数 --2
//在模板无法生成所需代码时,单独定义一个
template <> void swap< job>(job &, job &);
可以简写为:
template <> void swap(job &, job &);
因为job是一个具体化的,因此不需要< job>重申

    //模板都需要在main前声明,main后提供定义
    int main()
    {
        double u, v;
        swap(u, v); //没有对应的具体模板和具体函数,使用模板
        
        job a, b;
        swap(a, b); //有具体化的模板函数,使用之
    }

实例化:编译器通过模板生成的对应具体数据类型的函数。分显式和隐式
显式实例化template void swap< int>(int, int) //意思是:使用swap()模板生成int类型的函数定义
显示具体化template <> void swap< int>(int &, int &)
template <> void swap(int &, int &) //意思是:不使用swap()模板,使用专门为int
// 类型显示定义的函数定义
在同一个文件中使用同一种类型的显示实例和显示具体化将出错

隐式实例化(让编译器根据参数自己判断),显式实例化和显示具体化统称 具体化,均表示使用具体类型的函数定义,而不是通用描述

函数重载,函数模板,函数模板重载–重载解析P290 8.5.5
1.创建候选函数列表,包括函数和模板函数
2.创建可行函数列表,都是参数数目正确的函数
3.匹配查询:
//整数类型不可被转换为指针类型
1.完全匹配,常规函数优先于模板
2.提升转换,如char和shorts 自动转换为int, float转换为double
3.标准转换,如int转换为char,long转换为double, char到float
4.用户自定义

    完全匹配&最佳匹配
    允许从实参到形参无关紧要的转换 P290-----int实参 和 int&匹配
    
    有时候,完全匹配的有两个,但是:指向非const数据的指针和引用优先 与非const指针和引用参数匹配
    然后const和非const的区别只适用于指针和引用指向的数据
    显式优先于隐式
    
    部分排序规则
    最具体--编译器认为哪种类型执行的转换最少

重载解析将寻找最匹配的函数。
如果只存在一个这样的函数,用
如果存在多个,其中只有一个非模板函数,则选择该函数
如果存在过个合适的函数,且都是模板函数,但只有一个函数比其他函数更具体,转换更少,则选择该函数
如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他更具体,则报错,

自己选择
可以将模板实体定义在文件开头,充当原型。从而无需编译器选择

第九章

头文件内容:函数原型,使用#define和const定义的符号常量, 结构声明, 类声明, 模板声明,内联函数
结构变量知识告诉编译器如何创建变量,相当于创建了一个数据类型。
用户自动以的头文件,用双引号, 编译器首先查找工作目录或者源代码目录,如果没找到,则在标准位置查找

#pragma once // 防止文件被包含多次
#ifndef XX_H_
#define XX_H_
#endif

并不能防止包含两次,只是让编译器忽略第一次包含之外的内容

名称修饰,编译器按照一定规则将函数名和参数列表加密编码以确定唯一性。
不同编译器对同一个函数生成的修饰名称可能不同,从而无法链接。

存储的连续性、作用域和链接性

C++描述存储方式
自动存储持续性、静态存储持续性、动态存储持续性
线程存储持续性:变量使用thread_loccal声明,则线程生命周期内,变量存在。

链接性
描述 名称 如何在不同单元间共享。
性质为为外部的那些名称可以在文件间共享。
为内部的只能在一个文件内的函数间共享。自动变量的名称没有链接性,无法被共享。

作用域
局部,全局(文件作用域,定义位置到文件结尾)
自动变量的作用域为局部 静态变量作用域取决于它被如何定义的
函数原型作用域,旨在参数列表的括号内可用
在类中声明的成员作用域是整个类
在名称空间声明的变量作用域是整个名称空间
C++不能在代码块内定义函数

自动存储持续性
默认情况下:函数声明/定义中的参数和变量存储持续性为自动,作用域为局部,没有链接性
当代码块内外存在同名定义,内部掩盖外部定义,直到代码块结束。
自动变量 初始化:值已知的表达式
自动变量的数目随着程序的开始结束而增减–栈–放在相邻的内存单元中
寄存器变量 register–占位置而已,C++中不用了

静态存储持续性:–程序执行期间存在,数目不变,编译器只将他们放在固定的内存块中存储静态变量
如果没有显式的初始化,编译器把他们设置为0.静态数组和结构成员的每一位都为0

外部链接性 代码块外声明它 其他文件使用
内部链接性 代码块外声明它,用static限定符(比外部链接性多了个static) 类似自动变量,但是自动变量用到时才创建,而这个程序一开始就在 当前文件可用
无链接性 代码块内部声明它,static限定符P310 当前代码块可用

静态变量初始化默认零初始化,常量表达式初始化–两者被称为静态初始化,编译时初始化
动态初始化–编译后初始化 .一开始,所有常量都被初始化为0,之后根据实际再更正

外部链接性–外部变量–静态–全局变量
每个使用外部变量的文件都要声明它,且只能有一次定义。
1.定义声明–定义 给变量分配空间
2.引用声明–声明 不分配空间,引用已有的变量 extern 不用初始化,否则成了定义声明

作用域解析符:: 放在变量名前面,表示使用变量的全局版本

外部性,多文件程序不同部分共享数据;内部性,同一个文件不同函数间共享数据
无链接性: 代码块内static 一直存在,不可共享,两次调用之间不用对这个变量初始化

9.2.7 说明符和限定符

存储说明符:说明有关存储类型 static extern auto register thread_local mutable
限定符:cv-限定符
const ——定义类型为常量 类型。直接修改编译不通过,间接修改则结果未定义
volatile ——定义类型为易变 类型。 意味着该变量可能在执行过程中意外改变,是程序之外因素如硬件改变引起的该关键字告诉编译器不要对易变类型进行某些优化
例如:两次使用了某个变量的值,编译器会将值缓存在寄存器中,而不是查找两次,这种优化,假设两次之间变量的值不会变化。不将变量声明为volatile,则进行这种优化。如果,因为硬件改变了值,这种优化存在漏洞。
const volatile int i = 10; const 的可修改了

mutable 即使结构(类)变量为const,其对应的某个成员也可以被修改

#include <iostream>
using namespace std;
class A
{
private:
    int a;
    mutable int f;
public:
    A(int b);
    void setA(int a) const;
};

A::A(int b) : a(b) {}

void A::setA(int c) const
{
    int b = c;
    f = b + 1;
}
int main()
{
    A _a{1};
    return 0;
}
const

默认的全局变量的链接性是外部的,加上const就成了内部的,和加上了static一样p318
这样,头文件中的一组常量加上const,同一程序多个文件使用它时,预处理器将其包含到每个源文件而不报错,因为对于每个文件而言,这些常量都是内部的

如果仍然和常规变量一样是外部的,那根据单定义规则,上述将出错,也就是只能在一个文件中包含前面的声明,其余文件必须加上extern关键字,且只有未使用extern的声明才可以初始化。

当希望某个常量是外部的 可以用external 覆盖const。这样必须在所有使用该常量的文件中使用extern关键字来声明它
external const int a = 4; 变为外部了
重点 http://t.csdn.cn/vx9WT :C++:头文件中的const全局变量

    // 头文件中包含的变量声明,在不同cpp文件中各自赋予不同的值--ok,
    //(const 无法修改)头文件中定义并赋予初始值的变量加不加const, 都没有报警,且正常输出----这是因为第一次编译产生的.o文件残留,vscode与devc++会出现此种问题,vs则依然直接报错
    // 一个cpp文件外部性的全局变量,另一个文件extren后可以重赋值, 除非它加了static
    //包含头文件,得给绝对路径……这也是vscode的臭毛病
9.2.8函数和链接性

默认外部。可以加上external 标记函数声明的实体在其他文件
若只想内部,则声明,定义都加上static
非内联函数,只能包含一个定义,外部性的函数来说只能有个一个文件定义了函数(如库文件),使用了该函数的文件 都要要有其原型
内联函数允许将定义放在头文件,被多个文件包含,相当于到处都定义了一遍,但是定义必须一样。

9.2.10内存动态分配–只被new和delete掌握

编译器三块独立内存:静态变狼,自动变量,动态变量
int * pi = new int (9)
int * ar = new int [4] {2,4,5,6}
new失败则异常

运算符new 和new[]分别调用如下函数

//分配函数
void * operator new(std::size_t);   
void * operator new[](std::size_t)
//分配函数
void * operator delete(std::size_t);   
void * operator delete[](std::size_t);

int *pi = new int; 
int *pi = new(sizeof(int));
定位new运算符

指定要使用的内存位置,返回要到的地址,里面没有数据哒

#include <new>
然后将new运算符用于提供了所需地址的参数
struct charff{...};
char buf[6];

charff *p1, *p2;
//通常用法,放在堆中
p1 = new charff
p2 = new int[20]
//指定了位置,放在buf中
p1 = new (buf) charff;
p2 = new (buf) int[20]

注意:这些都是从指定的内存块的开头要内存。
int a[20];
int *p;

a[0] = 12;
p = new(a) int;
*p = 88;

这样,88会覆盖12 在a[0]
定位new 和常规new区别
1.
如果内存格式与需要分配的格式不同,程序会使用(void*)将内存格式强制转换成需要的
定位运算符new不管那些内存单元被使用,也不查找未使用的内存块。让程序员自己管,提供一个偏移量

//从buf起始地址开始偏移了N个int大小的内存
p = new (buf + N * sizeof(int)) int[N] 

但是常规new没有这些烦恼,因为它把他放在了堆中
2.
常规new会自己找新块 ,定位new不会
3.
常规new,delete后,块可复用
定位new,使用的buf是静态内存时,不可以delete,然后delete只能清楚常规new分配的堆内存,不归delete管,当然,如果buf是new创建的,那delete就可以

定位new可以将信息放在特定的硬件地址处
定位new返回地址,转换为 void*

定位new 调用一个接收两个参数的new()–> 指定请求的字节数指定的位置
int *p = new int -->new(sizeof(int))
int p = new(buf) int -->new(sizof(int),buf)

9.3 名称空间

声明区域、潜在作用域,(实际)作用域

定义一个新的声明区域来创建命名的名称空间,避免与另外一个名称空间发生冲突,同时允许程序其他部分使用该名称中的内容

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中,因此,在名称空间中声明的名称链接性是外部的,除非引用了常量
还存在一个系统给的全局名称空间
作用于解析符 ::
未被装饰的名称–未限定的名称;包含名称空间的名称–限定的名称

using声明和using编译指令
using声明使特定的标志符可用 之后就不需要名称前缀解析 就和普通变量一样,可以覆盖全局变量
using Jill::aa; // using声明
using声明之后不可以定义同名变量,不然会报错多次定义,但是你可以直接用解析符直接用
using编译指令使整个名称空间可用

using namespace std;
namespace a{
    int aa;    
}
int main(){
    using namespace a;
    aa = 2;
    double aa;
    aa = 3.1;
    std::cout << aa << std::endl;
    std::cout << a::aa <<std::endl;
    return 0;
}

本地新的同名变量可以覆盖其他名称空间里的同名变量,但是可以通过解析符调出来
using编译指令的空间名称只能在一个大的代码块中生效
//这个也是能运行的

using namespace std;
int main()
{
    using namespace a;
    aa = 2;
    double aa;
    aa = 3.1;
    cout << aa << endl;
    cout << a::aa <<endl;
    return 0;
}

或许避免使用编译指令
尽量std::cout using std::cout这类
main前std是将所有内容都放到全局名称空间中,方便
main后std是将头文件放到名称空间std中,然后using将该名称空间在main中可用

名称空间可以套娃,解析符套娃而已
namesapce a {
namesapce b {}
}
C++17之后可以这样:
namespace a:: b::c::d{
}
using namespace a; 则a b 都导入了
或一个空间包含其他空间的内容
namespace aa{
using bb::c;
}
访问方式 std::cin >> aa::c //省略了c的前缀bb::
std::cin >> bb::c 也是ok的
要是没冲突
using namespace aa;
cin >> c; //using命令是可以传递的
可以给名称空间创建别名: namespace ml = my_love::girfild:: … (后续可以有一串) 使用: using ml::name 简化对嵌套名称空间的使用

未命名的名称空间,不可以using 只能本地使用

名称空间指导规则

1.使用在命名的名称空间中声明的变量,不使用外部/静态全局变量
2.函数库和类库,放到名称空间中
3.不要在头文件中使用using编译指令。因为,其他文件会包含头文件,☆
4.导入名称,用作用域解析运算符:: 或者using声明
5.using声明,首选将其作用域设置为局部而不是全局。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值