本人最近学习C++primer,顺带记录自己的学习。
第一章 开始
1.1编写一个简单的C++程序
每个C++程序都包含一个或多个函数,其中一个必须命名为main函数。
1.2认识输入输出
对象 | 用途 |
cin | 标准输入 |
cout | 标准输出 |
cerr | 标准错误 |
clog | 用来输出程序运行时的一般性信息 |
1.3注释简介
两种注释:
单行注释://
多行注释:/*(注释内容)*/
1.4控制流
while:一般适用于不知道循环次数
for:一般适用于知道循环次数的;
if:条件判断
1.5类简介
没找到资源,所以看着比较累,不建议看
第二章 变量和基本类型
2.1基本内置类型
类型转换
建议不要混用带符号类型和无符号类型。
字面值常量
一个形如42的值被称作字面值常量,这样的值一望而知。每个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型(书中原话)。
转义字符
2.2变量
变量定义
基本形式:类型说明符,随后紧跟着一个或者多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。
初始值
初始化和赋值是两个完全不同的的操作,初始化的含义是创建变量的时候赋予一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。
默认初始化
如果定义变量没有指定初始值,则变量被默认初始化。
****特殊情况:定义在函数体内部的内置类型变量将不被初始化,其值是未定义的,建议初始化每个内置类型的变量。
//示例
int aa = 0 , bb , cc;
double dd = 3.14;
//列表初始化,用花括号{}初始化变量
int units_sold = {0};
int units_sold{0};
变量的声明和定义
变量的声明:规定了变量的类型和名字
变量的定义:除了声明之外,还需要申请内存空间
如果想声明一个变量,而非定义它,需要使用extern关键字
****变量能且只能被定义一次,但是可以被多次声明。
extern int i ; //声明i,在全局中声明,在函数中定义
int j; //声明并定义
标识符
由字母、数字、下划线组成,其中必须以字母或者下划线开头,对大小写敏感。
名字与作用域
作用域:C++中大多数作用域都用花括号分隔。
作用域中一旦声明了某个名字,它所嵌套的所有作用域都能访问改名字。同时允许在内层作用域中重新定义外层作用域中已有的名字。
****如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。
#include<iostream>
using namespace std;
int i = 10; //全局global
int main()
{
int i = 5; //局部local
cout<<"i="<<i<<endl; //使用的是局部变量
cout<<"::i="<<::i<<endl; //使用的是全局变量
}
2.3复合类型
定义:复合类型是基于其他类型定义的类型。
引用(必须初始化):为对象起另外一个名字。
****引用本身不是对象,所以不能定义引用的引用
****引用要和绑定的对象严格匹配
****引用类型的初始值,必须是一个对象
****左值引用和右值引用的问题(本文未提及)(这里的引用是值左值引用)
指针
指针本身就是一个对象。允许对指针赋值和拷贝。指针无须在定义的时候赋值。
(1)利用指针访问对象
如果一个指针指向了一个对象,则允许使用解引用符(*)来访问对象。
(2)空指针 nullptr(C++11的新标准)
(3)void * 指针
可以用于存放任意对象的地址,但不能对void *指针解引用,需要强转为特定类型才可以解引用。
(1)指向指针的指针
**表示指向指针的指针
***表示指向指针的指针的指针
(2)指向指针的引用
不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。
2.4const限定符
const用于定义一个变量,它的值不能被改变。const对象必须初始化。
默认状态下,const对象仅在文件内有效。当多个文件出现了同名的const变量时,等同于在不同文件中 分别定义了独立的变量。 如果想让const变量在文件间共享,则使用extern修饰。
(1)const的引用
允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。一 般引用的类型必须与其所引用对象的类型一致,特殊情况是表达式。
补充概念
(2)指针和const
****所谓指向常量的指针或者引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,自觉的不去改变所指对象的值。
常量指针 const 数据类型 * 变量名; 不能通过解引用的方法修改内存地址的值
****指向的对象可以改变
****一般用于函数的形参,不希望在函数中修改内存地址的值
****如果形参的值不需要改变,建议加上const修饰,增加 程序的可读性
指针常量 数据类型 * const 变量名; 指向的对象不可改变。
****在定义的时候必须初始化,否则没有意义;
****可以通过解引用的方法修改内存地址的值(有没有感觉和引用的功能差不多)
常指针常量 const 数据类型 * const 变量名;
既不能改变指向的对象,也不能修改内存地址的值。
(3)顶层const(感觉没必要,更绕了)
top-level const 表示指针本身是个常量
low-level const表示指针所指的对象是一个常量。
(4)constexpr和常量表达式
常量表达式是指值不会改变,并且在编译过程就能等到计算结果的表达式。
C++新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达 式。
2.5处理类型
类型别名
(1)使用关键字typedef
(2)别名声明
using SI = Sales_item; //SI是Sales_item的同义词
auto:让编译器通过初始值来推断变量的类型
decltype:选择返回操作符的数据类型。只等到数据类型,不实际计算表达式的值
语法:decltype(expression) var;
1)如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
2)如果expression是一个函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void *)。
3)如果expression是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是expression的引用。
4)如果上面的条件都不满足,则var的类型与expression的类型相同。
2.6自定义数据结构
数据结构
struct 结构体名
{
//变量
};
数据结构是一组相关的数据元素组织起来,然后使用它们的一种策略和方法
C++11中支持在结构体中有成员函数,但不建议这么使用。
C++11中,定义结构体变量可以不写struct关键字
预处理
#idndef SALES_DATA_H
#define SALES_DATA_H
#endif
****一般把预处理变量的名字全部大写。
第三章 字符串、向量和数组
3.1 命名空间的using声明
声明形式:using namespace::name;
例如:using std::cin;
3.2标准库类型string
#include<string>
using std::string; //可以用using namespace std;
①初始化
string s1;
string s2(s1);
string s2 = s1;
string s3("value"); //直接初始化
string s3 = "value"; //拷贝初始化
string s4(n,'c'); //s4的内容是n个c
C++标准委员会允许把字面值和字符串字面值转换成string对象。字面值和string是不同的类型。
②操作
s.empty(); //判空
s.size(); //返回字符的个数
s[n]; //第n个字符的引用
s1+s2; //字符串的拼接
// = 赋值 == 判断相等 !=判断不等
// <, <=,> ,>= 用的可能少
补充getline函数(p87)
string buffer;
while(getline(fin,buffer)) //fin是文件指针
{
cout<<buffer<<endl;
}
③处理string对象中的字符
C++程序的头文件应该使用cname,而不应该使用name.h的形式,例如#include<cstring>
函数的介绍
④基于范围的for语句(C++11)
for(declaration:expression)
statement
3.3标准库类型vector
包含头文件#include<vector>
vector表示对象的集合,其中所有对象的类型都相同
vector是一个类模板,而不是类型(初学者可能不懂类模板的概念,不要太在意,后续学习的时候就知道了)
(1)定义和初始化vector对象
如果用圆括号,那么提供的值是用来构造vector对象的
如果用花括号,则是使用列表初始化该vector对象
vector是模板而非类型,由vector生成的类型必须 包含vector中元素的类型,例如vector<int>
这边我截了书中的内容,可以看一下
(2)向vector对象添加元素(删除)
void push_back(const T& value) //在容器的尾部追加一个元素
void emplace_back(...) //在容器的尾部追加一个元素,用于构造元素
void pop_back(); //从容器尾部删除一个元素
先定义一个空的vector对象,在运行的时候使用push_back()向其中添加具体值
(3)其他vector操作
结合for的范围循环
只能对确认已存在的元素执行下标操作。
3.4迭代器的介绍
支持的基本操作:赋值(=) 解引用(*) 比较(==和!=) 遍历(++从左到右遍历)
迭代器运算符
迭代器的类型
容器名<元素类型>::iterator 迭代器名;
容器名<元素类型>::const_iterator 迭代器名;
迭代器失效的问题(补充)
resize() reserve() assign() push_back() pop_back() insert() erase()可能会导致迭代器失效
3.5数组
****使用数组下标的时候,通常将其定义为size_t类型
****定义数组必须指定数组类型,不允许用auto推断
****不存在引用的数组
****如果两个指针指向不相关的对象,则不能进行对这两个指针进行比较
定义和初始化内置数组
数组指针也是迭代器(还是天然的)(C++也提供了array静态数组)
经验:
①初始化可以使用memset函数
②一维数组用于函数的参数时,必须将数组长度也传进去。例void func(int *arr,int len);或者void func(int arr[],int len);
③用new动态创建一维数组:
声明语法:数据类型 * 指针 = new 数组类型[数组长度];
释放一维数组的语法:delete [] 指针;
注意:不要用delete释放同一块内存两次,第二次相当于操作野指针,所以释放内存之后将指针置为nullptr。
声明普通数组时,数组长度可以使用变量,相当于在栈上动态创建数组,并且不需要释放。
如果内存不足,调用new会产生异常,导致程序终止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,便不会产生异常,程序正常退出。
了解qsort函数
④C风格的字符串
其他的strncpy()(有坑) 、strcpy()、strcat()、strncat()、strchr()、strrchr()、strstr()前四个使用有可能会导致数组越界。
经验:字符串在每次使用前都要初始化,减少出错的概率
vs中,要使用C标准的字符串操作函数,需要在源代码文件最上面加
#define _CRT_SECURE_NO_WARNINGS
3.3.5与旧代码的接口
之前介绍过使用字符串字面值来初始化string对象,但是反过来就不成立了,所以C++提供了c_str()函数
string s("Hello World");
const char * str = s.c_str();
3.6 多维数组
声明二维数组的语法:数据类型 数组名[行数][列数];
行指针:数据类型 (*行指针名)[行的大小]; //行的大小即数组的长度
①将二维数组传递给函数时
void func(int (*p)[n],int len);
void func(int p[][n],int len);
②多维数组
好像没什么可以说的,理解二维数组。
第四章 表达式
4.1 基础
重载运算符:为已经存在的运算符赋予另一种含义
左值、右值: 当个一个对象被用作右值的时候,用的是对象的值(内容);
当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置);
优先级:除了熟知的运算优先级,在不明确的优先级的情况,按自己想要的逻辑加括号就行。
4.2 算术运算符
注意:%参与取余运算的运算对象必须是整数类型;
4.3 逻辑和关系运算符
逻辑与(&&)和逻辑或(||)运算符都是先求左侧运算对象的值再求右侧运算对象的值,简称短路求值。
4.4赋值运算符
赋值运算符满足右结合律
不要混淆相等运算符和赋值运算符
if( i = j)
if( i == j)
4.5递增和递减运算符
递增运算符++
递减运算符--
4.6成员访问运算符
点运算符和箭头运算符
n=(*p).size();
n=p->size();
4.7条件运算符(三目运算)
condition? expression1 : expression 2;
如果condition为真,就执行expression1,否则就执行expression2
4.8位运算符(p137)
有点复杂,自己看书吧!!!!!
4.9sizeof()运算符
sizeof运算符返回一条表达式或益哥类型名字所占的字节数,其所得值是一个size_t类型,是一个常量表达式
语法:sizeof(type) ; (推荐版) sizeof expr; (不推荐)
对指针使用sizeof,值是定值
4.10逗号运算符
逗号运算符含义两个运算对象,按照从左向右的顺序依次求值。
4.11类型转换
隐式转换
显示转换
命名的强制的类型转换(p144)
dynam_cast 只用于包含虚函数的类
语法:派生指针 = dynamic_cast<派生类类型 *>(基类指针);
如果转换成功,dynamic_cast返回对象的地址,如果失败,返回nullptr
****由于强制类型转换干扰了正常的类型检查,因此建议避免强制类型转换
第五章语句
5.1简单语句
(1)空语句
; //这是一条空语句
(2)复合语句
复合语句是指用花括号括起来的(可能为空的)语句和声明的序列,复合语句也被称作块(block)。
{}
5.2语句作用域
定义在控制结构当中的变量只在相应语句的内部可见,一旦语句结束,变量就超出其作用范围。
5.3条件语句
(1)if语句
(2)switch语句
case关键字和它对应的值一起被称为case标签
case标签必须是整型常量表达式
如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显示的中断了这一过程。
dedault 标签:如果没有任何一个case标签能匹配上switch表达式的值,程序将执行紧跟在default标签 后面的语句。
5.4迭代语句(p165)
//(1)while语句
while(condition)
statement
//(2)传统for语句
for(initializar;condition;expression)
statement
//for语句中定义的对象只在for循环体内可见
//(3)范围for语句
for(declaration : expression)
statement
//do while语句
do
statement
while(condition)
5.5跳转语句
break只能出现在迭代语句或者switch语句内部。仅限于终止离它最近的语句,然后从这些语句之后的第 一条语句开始执行。
continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。
goto的作用是从goto语句无条件跳转到同一函数内的另一条语句(容易造成控制流混乱,应禁止使用)
return
5.6try语句块和异常处理
C++中异常处理包括:throw表达式 、 try语句块
try 和 catch,将一段可能抛出异常的语句序列括在花括号里面构成try语句块。catch子句负责处理代码抛出的异常。
throw表达式语句,终止函数的执行。抛出一个异常,并把控制权转移到能处理该异常的最近的catch字 句
第六章 函数
6.1函数基础
(1)形参和实参
实参的类型必须与对应的形参类型匹配。 函数的调用规定实参数量应与形参数量一致。
(2)局部对象
形参和参数体内部定义的变量统称为局部变量,它们对函数而言是"局部"的,仅在函数的作用域内可 见,同时局部变量还会隐藏外层作用域中同名的其他变量。
自动对象:只存在于块执行期间的对象。
局部静态对象:在程序的执行路径第一次经过对象定义语句时候进行初始化,并且直到程序终止才会被 销毁。
(3)函数声明
函数的三要素:(返回类型、函数名、形参类型)。
函数可被声明多次,但只能被定义一次。
(4)分离式编译
分离式编译允许把程序分割到几个文件中去,每个文件独立编译。
编译->链接
6.2参数传递
当形参是引用类型,这时它对应的实参被引用传递或者函数被传引用调用。当 实参被拷贝给形参,这样的实参被值传递或者函数被传值调用。
(1)传值参数
(2)被引用参数
(3)const形参和实参
(4)数组形参
为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
void print(const int *);
void print(const int[]);
void print(const int[10]);
//这三个函数等价
int *mp[10]; //10个指针构成的数组
int(*mp)[10]; //指向含有10个整数的数组指针
(5)含有可变形参的数组
initializer_list
for vv(initializer_list<string> i)
6.3返回类型和return语句
return ; //无返回值
return expression; //有返回值
函数完成后,它所占用的存储空间也会随着被释放掉。
****返回局部对象的引用是错误的;返回局部对象的指针也是错误的。
6.4 函数重载
函数重载:如果同一作用域内的几个函数名字相同但形参列表不同(形参个数、数据类型、排列顺序);
不允许两个函数除了返回类型不同,其他的所有要素都相同;
重载与作用域
如果在内作用域中声明,将隐藏(书面词)外作用域中声明的同名实体
属于概念过渡,用模板即可
****使用重载函数时,如果数据类型匹配不上的话,编译器将会采用类型转换,如果转换之后有多个函数能匹配上,编译将报错
****重载函数有默认参数时,调用函数时,可能导致匹配失败
6.5 特殊用途语言特性
①默认实参
typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char background = ' ');
函数调用时,实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参。
****当设计含有默认实参的函数时,需要合理设置形参的顺序。一旦某个形参被赋予了默认值,它后面的所 有形参都必须有默认值。
②内联函数 使用inline来声明内联函数
用于优化规模较小、流程直接、频繁调用的函数
****运行速度可能快一点,但是占内存
****内联函数不能递归
③constexpr函数
constexpr函数是指能用于常量表达式的函数。
6.6函数匹配
①确定候选函数和可选函数
②寻找最佳匹配
6.7函数指针
函数指针指向的是函数而非对象。
void useBigger (const string &s1, const string &s2, bool pf(const string &,
const string &));
//等价于
void useBigger (const string &s1, const string &s2, bool (*pf)(const string &,
const string &));
补充
申请内存分配失败的用法
int *a = new (std::nothrow) int [10000000000001]; //会返回空
结构体的相关知识
#pragma pack(1) //内存对齐
struct S_struct
{
char name[21];
int age;
//声明各种变量
//C++中可以声明函数,但不提倡
};
int main()
{
struct S_struct people; //C++中可以不写struct
//变量名访问结构体的变量 people.age;
struct S_struct *pst = &people;//结构体指针
//指针访问结构体的变量的方法:(*pst).age 或者 pst->age;
//结构体数组
struct S_struct peoples[3];
}
结构体嵌入数组
#include<iostream>
using namespace std;
struct programs
{
char name[50]; //项目名称
int sale[2][3] = {11,12,13,14,15,16}; //C++运行有缺省值; 结构体嵌入数组
};
void func(programs *pst)
{
//使用结构体中的二维数组
for(int i = 0 ; i < 2 ; i++)
for(int j = 0 ; j < 3; j++)
cout<<pst->sale[i][j]<<endl;
}
int main()
{
programs progms;
func(&progms);
}
结构体中的指针(需要注意memset的使用)
#include<iostream>
#include<string>
using namespace std;
struct st_demo
{
int a;
int *p;
};
int main()
{
st_demo demo;
memset(&demo,0,sizeof(demo));
demo.a=1;
demo.p = new int[100]; //动态分配内存
cout<<"sizeof(demo)="<<sizeof(demo)<<endl;
cout<<"调用前:"<<"demo.p="<<demo.p<<endl;
//memset(&demo,0,sizeof(st_demo)); //会产生内存泄漏
memset(demo.p, 0, 100 * sizeof(int)); //清空指向内存的内容
cout<<"调用后:"<<"demo.p="<<demo.p<<endl;
}
共同体
//声明共同体
union U_demo
{
int a;
double b;
};
//共同体能够存储不同的数据类型,但是,在同一时间只能存储其中的一种类型
/*
全部的成员使用同一块内存
共同体中的值为最后被赋值的那个成员的值
共同体常用于节省内存(嵌入式系统)
struct st_demo
{
int no;
union //匿名共同体
{
int a;
double b;
};
};
//应用场景
//当数据项使用两种或更多种格式(但不会同时使用),可节省空间
//用于回调函数的参数(相当于支持多种数据类型)网络编程那一块
枚举
enum colors{red,yellow,bule,other};
colors cc = red; //red=0的
switch(cc)
{
case red: cout<<"红色。\n";break;
case yellow: cout<<"黄色。\n";break;
case bule: cout<<"蓝色。\n";break;
default :cout<<"默认。\n";
}
引用即指针常量(例int *const a;)
如果引用的数据对象类型不匹配,当引用为const时,C++将创建临时变量,让引用指向临时变量。代码演示
int main()
{
//int &ra = 1; 编译会失败
const int & ra = 1; //编译没问题
//相当于先创建 int tmp = 1; const int &ra = tmp;
}
引用用于函数的返回值
返回值的数据类型 & 函数名(形参列表);
****返回局部变量的引用,其本质是野指针
****可以返回函数的引用形参、类的成员
int & func(int &no)
{
no = 100;
cout<<"no的地址:"<<&no<<",no="<<no<<endl;
return no;
}
int main()
{
int bh = 10;
int &receive = func(bh);
cout<<"bh的地址:"<<&bh<<",bh="<<bh<<endl;
cout<<"receive的地址"<<&receive<<",receive="<<receive<<endl;
}
函数传值、传地址、传引用
不需要在函数中修改实参
****实参为数组时,用const指针
****实参是类时,使用const引用
需要修改实参
实参是数据内置类型,则使用指针,因为看到函数使用func(&x),就表示函数会修改x