请选择 进入手机版 | 继续访问电脑版

硬汉嵌入式论坛

 找回密码
 立即注册
查看: 2796|回复: 22
收起左侧

Note of C++ Primer

  [复制链接]

14

主题

207

回帖

254

积分

高级会员

积分
254
发表于 2017-12-23 12:36:55 | 显示全部楼层 |阅读模式
本帖最后由 龙之谷 于 2017-12-23 13:03 编辑

换到新论坛了,不知道发帖体验会不会更好一些,发个帖子试试水,同时也祝安福莱精益求精、越来越好。
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105942
QQ
发表于 2017-12-23 12:58:25 | 显示全部楼层
回复

使用道具 举报

747

主题

1048

回帖

3294

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3294
发表于 2017-12-23 15:29:53 | 显示全部楼层
谢谢支持
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-1 22:43:11 | 显示全部楼层
2018-01-01
1.一个函数的定义包含四部分:返回类型(return type)、函数名(function name)、一个括号包围的形参列表(parameter name)以及函数体(function body)
2.内置类型(built-in type),即语言自身定义的类型。
左花括号(curly brace)
语句块(block of statement)
源文件(source file)
集成开发环境(Integrated Developed Environment, IDE)
3.如果一个名为v的变量的类型为T,通常说“v具有类型T”,或“v是一个T类型变量”。
4.①在Windows系统中运行一个可执行文件,需要提供可执行文件的文件名,可以忽略其扩展名.exe
$ prog1
在一些系统中,即使文件就在当前目录或文件夹中,也必须显式指出文件的位置,此时,可以键入
$ .\prog1
“.”后跟一个反斜线指出该文件在当前目录中
②在UNIX系统中运行一个可执行文件,需要使用全文件名,包括文件扩展名
$ a.out
如果需要指定文件位置,需要一个”.”后跟一个斜线来指出可执行文件位于当前目录中。
$ ./a.out
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-2 23:27:40 | 显示全部楼层
2018-01-02
1.“流”(stream)想要表达的是,随着时间的推移,字符是顺序生成或消耗的,一个流就是一个字符序列。
2.        cin  --标准输入(standard input),搭配输入运算符>>
cout --标准输出(standard output),搭配输出运算符<<
cerr --标准错误(standard error)
clog--输出程序运行时的一般性信息
3.endl--操纵符(manipulator)的特殊值,效果是结束当前行,并将与设备关联的缓冲区(buffer)中的内容刷到设备中。
4.前缀std::指出其后名字是定义在std的命名空间(namespace)中的,标准库定义的所有名字都在命名空间std中。
5.举例
输出运算符左侧的运算对象必须是一个ostream对象,右侧的运算对象是要打印的值
std::cout << “Enter two numbers:” << std::endl;
输入运算符从给定的istream中读入数据,并存入给定对象中,返回左侧的运算对象作为其计算结果
(std::cin  >>  v1)  >>  v2
6.按照报告的顺序来逐个修正错误,是一种好习惯。因为一个单个错误常常会具有传递效应,导致编译器在其后报告比实际数量多得多的错误信息。修正了一小部分明显的错误后就重新编译,这就是所谓的“编辑-编译-调试”(edit-compile-debug)周期。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-3 22:33:27 | 显示全部楼层
2018-01-03
1.(class)C++中用来定义我们自己的数据结构,C++的一个设计焦点就是能定义使用上像内置类型一样自然的类类型(class type)
2.使用文件重定向,当你测试程序时,反复从键盘敲入销售记录作为程序的输入,是非常乏味的。大多数操作系统支持文件重定向,这种机制允许我们将标准输入和标准输出与命名文件关联起来:
$ addItems <infile> outfile
假定$是操纵系统提示符,加法程序已经编译为名为addItems.exe的可执行文件,则上述命令会从一个名为infile的文件读取销售记录,并将输出结果写入到一个名为outfile的文件中,两个文件都位于当前目录中。
3.成员函数(member function),是定义为类的一部分的函数,有时也被称为方法(method)
4.大多数编程语言通过两种方式来进一步补充其基本特性:一是赋予程序员自定义数据类型的权利,从而实现对语言的扩展;二是将一些有用的功能封装成库函数提供给程序员。
5.计算机以比特序列存储数据,每个比特非01,常用2的整数次幂个比特作为块来处理内存,可寻址的最小内存卡称为“字节”(byte),存储的基本单元称为“字”(word)
6.        ①当赋值给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。
②当赋值给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。
③当一个算数表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。
7.转义序列(escape sequence),均以反斜杠\开始。
8.通过添加前缀和后缀,能够改变整型、浮点型和字符型字面值的默认类型。如:L’a’                u8”hi!”                42ULL                3.14159L
字符和字符串字面值
前缀
类型
u
char16_t
U
char32_t
L
wchar_t
u8
char
整型字面值
U/u
unsigned
L/l
long
LL/ll
long long
浮点型字面值
F/f
float
L/l
long double
9. ①变量提供一个具名的、可供程序操作的存储空间。
②变量定义的基本形式是:首先时类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号隔开,最后以分号结束。
③当对象在创建时获得了一个特定的值,就说这个对象被初始化(initialized)了。
④初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。
10. ①声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)负责创建与名字关联的实体。
②如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式的初始化变量:
extern int i;        //声明i而非定义
int j;                        //声明并定义
任何包含了显式初始化的声明即成为定义。可以给由extern关键字标记的变量赋初值,但是这么做就抵消了extern的作用,不再是声明,而变成定义了:
extern double pi = 3.14159;                //定义
11.复合类型(compound type)是指基于其他类型定义的类型。
12.引用(reference)为对象起了另外一个名字,引用类型引用(refer to)另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名。
int ival = 1024;
int &refval = ival;                //引用必须被初始化
一旦初始化完成,引用将和它的初始值对象一直绑定在一起,因为无法令引用重新绑定另外一个对象,因此引用必须初始化。
因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
13.过去的程序常用到一个名为NULL的预处理变量(preprocessor variable)来给指针赋值,这个变量在头文件cstdlib中定义,值为0
int变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行。
14.对于复合类型声明,最简单的办法是从右向左阅读变量定义,离变量名最近的符号对变量的类型有最直接的影响。
15.        ①指针本事是一个对象,它又可以指向另外一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。
②顶层const(top-level const)表示指针本身是个常量,底层const(low-level const)表示指针所指的对象是一个常量。
③更一般的,顶层const可以表示任意的对象是常量,对任何数据类型都适用;底层const则与指针和引用等复合类型的进步类型部分有关。
④        int i = 0;
int *const p1 = &i;                        //不能改变p1的值,这是顶层const
const int ci = 42;                        //不能改变ci的值,这是顶层const
const int *p2 = &ci;                //不能改变p2的值,这是顶层const
const int *const p3 = p2;        //左底层,右顶层const
const int &r = ci;                        //用于声明的都是底层const

回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-4 23:05:30 | 显示全部楼层
2018-01-04
1.C++11规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式,声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
2.两种定义类型别名的方法
①关键字typedef
typedef double wages;        //wages是double的同义词
②别名声明(alias declaration),使用关键字using作为别名声明的开始,其后紧跟别名和等号,作用是把等号左侧的名字规定成等号右侧类型的别名。
using SI = Sales_item;                //SI是Sales_item的别名
3.C++11新引入auto类型说明符,用它让编译器替我们分析表达式所属的类型,和原来只对应一种特定类型说明符不同,auto让编译器通过初始值推算变量类型。
auto item = val1+val2;                //item初始化为val1和val2相加结果
4.从表达式的类型推断出要定义的变量的类型,但是不用该表达式的值初始化变量,使用类型说明符decltype,作用是选择并返回操作数的数据类型,编译器分析表达式并得到它的类型,却不实际计算表达式的值
decltype(f())        sum = x;                //sum的类型就是函数f的返回类型
编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型,sum的类型就是f被调用的话将会返回的那个类型。
注意:对于decltype所用的表达式来说,如果变量名加上括号和不加括号得到的类型不同,对于不加括号的变量,得到的结果是改变的类型;对于加上括号的变量,编译器就会把它当成一个表达式。
decltype((variable))(双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用。
5.自定义数据结构的两个关键字struct和class。
6.头文件保护符(header guard)
#ifndef XXX
#define XXX
.....
#endif
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-5 23:24:46 | 显示全部楼层
2018-01-05
1.using声明(using declaration),能够更简单的使用到命名空间中的成员。
using namespace::name;
有了以上声明,就无须专门的前缀(形如命名空间::)也能使用所需的名字了。
注意:        ①每个using声明引入命名空间中的一个成员。
②头文件不应包含using声明。
2.getline函数只要一遇到换行符就结束读取操作并返回结果(换行符也被读进来了)。
3.string::size_type类型,其实size函数返回的就是此类型的值,它是一个无符号类型的值,能够足够存放下任何string对象的大小。
4.C++标准库中除了定义C++语言特有的功能外,也兼容了C语言的标准库。C语言的头文件形如name.h,C++则将这些文件命名为cname。也就是去掉了.h后缀,而在文件名name之前添加了字母c,这里的c表示这是一个属于C语言标准库的头文件。
5.范围for(range for)语句,遍历给定序列中的每个元素并对序列中的每个值执行某种操作,语法形式为:
for(declaration : expression)
statement
expression是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration部分的变量都会被初始化为expression部分的下一个元素值。
string str(“some string”);
for(auto c : str)
cout << c << endl;
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-6 23:29:52 | 显示全部楼层
2018-01-06
1.标准库类型vector
①标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中每个对象都有一个与之对应的索引,索引用于访问对象。因为vector“容纳着”其他对象,所以它常被称作容器(container).
②要想使用vector,必须包含适当的头文件
#include <vector>
using std::vector;
③vector是类模板。模板本身不是类或函数,可以将模板看作是编译器生成类或函数写的一份说明。编译器根据模板创建类或函数的过程称为实例化(instantiation),当使用模板时,需要指出编译器应把类或函数实例化成何种类型。
对于类模板来说,通过提供一些额外信息来指定模板到底实例化成什么样的类,需要提供哪些信息由模板决定。提供信息的方式总是这样:在模板名字后面跟一对尖括号,在括号内放上信息:
vector<int> ivec;
vector<vector<string>>        file;
由vector生成的类型必须包含vector中元素的类型。
④列表初始值还是元素数量
vector<string> articles = {“a”, ”an”, ”the”};

vector<string> v1{“a”, “an”, “the”};                //列表初始化
vector<string> v2(“a”, ”an”, ”the”);                //错误
通常情况下,可以只提供vector对象容纳的元素数量而不用去初始值,此时库会创建一个值初始化(value-initialized)元素初值,并把它赋给容器中的所有元素。
vector<int> ivec(10);                //10个元素,每个都初始为0
vector<string> svec(10);        //10个元素,每个都是空string

vector<int> v1(10);        //10个元素,每个都初始为0
vectro<int> v2{10};        //1个元素,值为10
vector<int> v3(10, 1);        //10个元素,每个都初始为1
vector<int> v4{10,1};        //2个元素,分别为10和1
圆括号提供的值是用来构造(construct)vector对象的,如v1的初始值说明了vector对象的容量。
花括号可以表述成想列表初始化(list initialize)该vector对象,即初始化过程会尽可能的把花括号的值当成是元素初始值的列表来处理,只有在无法执行列表初始化时才会考虑其他初始化方式。比如,如果初始化时使用了花括号的形式但是又不能用来列表初始化,就要考虑用这样的值来构造vector对象了。
vector<string> v5{“hi”};        //列表初始化,v5由一个元素
vector<string> v6(“hi”);        //错误,不可用字符串字面值构建对象
vector<string>         v7{10};        //有10个默认初始化的元素
vector<string> v8{10, “hi”};        //由10个值为“hi”的元素
⑤常用vector操作
v.empty()                        判断是否为空
v.size()                                返回元素个数
v.push_back(t)                尾端添加一个值为t的元素
v[n]                                返回第n个位置上的元素的引用
v1 = v2                                拷贝替换
v1 = {a, b, c...};                拷贝替换
v1 == v2                        全等判断
v1 != v2                        不等判断
<,<=,>,>=                        比较
举例:
vector<int> v2;
for(int i=0; i!= 100; ++i)
v2.push_back(i);        //依次把整数放到v2尾端
⑥不能用下表形式添加元素:
vector<int> ivec;
for(decltype(ivec.size()) ix=0; ix!=10; ++ix)
ivec[ix] = ix;        //严重错误,ivec不包含任何元素
补:此时ivec[0]也是错误的,因为ivec为空对象,不包含元素
正确处理应为:
for(decltype(ivec.size()) ix=0; ix!=10; ++ix)
ivec.pushback(ix);
vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。
2.列表初始化(P39),C++定义了初始化的好几种不同形式,这也是初始化问题复杂性的一个体现。
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);
C++新标准以后,无论是初始化对象还是某些时候为对象赋新值,都可以使用这样一组由花括号括起来的初始值了。
3.迭代器(iterator)
①类似于指针类型,迭代器也提供了对对象的间接访问。
②有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置,其他所有情况都属于无效。
③和指针不一样的是,获取迭代器不是使用取地址符,由迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都有名为begin和end的成员。
begin负责返回指向第一个元素(或字符)的迭代器。
end成员则负责返回指向容器(或string对象)“尾元素的下一位置(one past the end)”的迭代器,也就是说,迭代器指示的是容器的一个本不存在的“尾后(off the end)”元素。这样的迭代器没有什么实际含义,仅是个标记而已,表示已经处理完了容器中的所有元素。end成员返回的迭代器常被称为尾后迭代器(off-the-end iterator)或者简称为尾后迭代(end iterator)。
特殊情况下如果容器为空,则begin和end返回的是同一个迭代器。
auto b=v.begin(), e=v.end();        //b e类型相同
④标准容器迭代器运算符
*iter                        迭代器iter所指元素的引用
iter->mem                解引用iter并获取该元素的名为mem成员
++iter                        令iter指示容器的下一个元素
--iter                        令iter指示容器的上一个元素
iter1 == iter2        判断是否相等
iter1 != iter2        判断是否不等
举例:
for(auto it=s.begin(); it!=s.end && !isspace(*it); ++it)
*it = toupper(*it);
⑤只有string和vector等一些标准库类型有下标运算符,而并非全部如此。类似,所有标准库容器的迭代器都定义了==和!=,但是他们中的大多数都没有定义<运算符。因此,只要我们养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。
⑥拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型。const_iterator和常量指针差不多,能读取但不能修改它所指的元素值。
vector<int>::iterator it;
string::iterator it2;
vector<int>::const_iterator it3;
string::const_iterator it4;
⑦只要两个迭代器指向的是同一个容器中的元素或尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。所谓距离指的是右侧的迭代器向前移动了多少位置就能追上左侧的迭代器,其类型名为difference_type的带符号整型数。
⑧迭代器完成二分搜索(text必须是有序的)
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (text.begin() + text.end()) / 2;
while(mid != end && mid != sought)
{
if(sought < *mid)
end = mid;
else
beg = mid + 1;
mid = beg + (end - beg) / 2;
}
4.①数组是一种类似于标准库类型vector的数据结构,但是在性能和灵活性的权衡上又与vector有所不同。
两者相似的地方是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在的位置访问。
不同之处在于,数组的大小是确定不变的,不能随意向数组中增加元素,因为数组的大小固定,因此对某些特殊的应用来说程序的运行时性能较好,但是相应的损失了一些灵活性。
②C++11新标准引入了两个名为begin和end的函数,这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为他们的参数:
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia);        //指向首元素的指针
int *last = end(ia);                //指向尾元素的下一位置的指针
③两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,也是定义在cstddef头文件中的机器相关的类型,差值可正可负,所以ptrdiff_t是一种带符号类型。
5.①字符串字面值这种结构是C++由C继承而来的C风格字符串(C-style character string)。
②C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。
③字符串存放在字符数组中并以空字符结束(null terminated)。以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符(‘\0’)。
④字符串函数
strlen(p)                        返回p长度,空字符不计在内
strcmp(p1, p2)                比较是否相等,可正可负可为0
strcat(p1, p2)                p2附加在p1之后,返回p1
strcpy(p1, p2)                p2拷贝给p1,返回p1
举例:传入此类函数的指针必须指向以空字符作为结束的数组:
char ca[] = {‘C’, ‘+’, ‘+’};                //不以空字符结束
cout << strlen(ca) << endl;        //严重错误,ca没有空字符结束
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-10 21:00:17 | 显示全部楼层
2018-01-07【住处断网,后补】
1.表达式由一个或多个运算对象(operand)组成,对表达式求值将得到一个结果(result),字面值和变量是最简单的表达式(expression),把一个运算符(operator)和一个或多个运算对象组合起来可以生成较复杂的表达式。
2.        一元运算符(unary operator),作用于一个运算对象的运算符。
二元运算符(binary operator),作用于两个运算对象的运算符。
3.对于含有多个运算符的复杂表达式来说,要想理解它的含义首先要理解运算符的优先级(precedence)、结合律(associativity)以及运算对象的求值顺序(order of evaluation)。
4.表达式要不然是右值(rvalue),要不然是左值(lvalue)。
复合表达式(compound expression)是指含有两个或多个运算符的表达式。
5.优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值,在大多数情况下,不会明确指定求值顺序
int a = f1() * f2();
f1()、f2()一定会在执行乘法之前被调用,因为毕竟相乘的是这两个函数的返回值,我们无法知道到底f1在f2之前还是之后调用。
对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为。
int i = 0;
cout << i << ”” << ++i << endl;                //错误
因为程序是未定义的,无法推断它的行为,编译器可能先求++i的值再求i的值,此时结果为1 1;也可能先求i的值,再求++i的值,输出结果是0 1;甚至还可能做完全不同的操作,因此表达式不可预知,无论编译器生成什么代码都是错误的。
6.        除法运算符,如果两个运算对象的符号相同则商为正,否则商为负。C++早期版本允许结果为负值的商向下或向上取整,C++11新标准则规定商一律向0取整。
取余运算符,如果m和n是整数且n不为0,则表达式(m/n)*n+m%n的求值结果与m相等。隐含的意思是,如果m%n不等于0,则它的符号和m相同。C++语言的早期版本允许m%n的符号匹配n的符号,而且商向负无穷一侧取整,这一方式在新标准中被禁止使用。
除了-m导致溢出的特殊情况,其他时候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n).
7.逻辑和关系运算符
①        关系运算符作用于算术类型或指针类型。
逻辑运算符作用于任意能转换成布尔值的类型。
②运算符                        功能                        用法
!                                逻辑非                !expr
<                                小于                        expr < expr
<=                                小于等于                expr <= expr
>                                大于                        expr > expr
>=                                大于等于                expr >= expr
==                                相等                        expr == expr
!=                                不相等                expr != expr
&&                                逻辑与                expr && expr
||                                逻辑或                expr || expr
③逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。这种策略称为短路求值(short-circuit evaluation)。
8.赋值运算符
①左侧运算对象必须是一个可修改的左值。
②结果的类型是左侧运算对象的类型,如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型,但需要注意,左右运算对象类型不同时,左侧应可由右侧对象的类型转换得到,否则为非法操作。
9.递增递减运算符由两种形式:前置版本和后置版本。
常用前置版本,首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。
后置版本也会将运算对象加1(或减1),但是求值结果是运算对象改变之前那个值的副本。
int i=0, j;
j = ++i;
j = i++;
除非必须,否则不要用递增递减运算符的后置版本。原因很简单,前置版本的递增运算避免了不必要的工作,它把值加1后直接返回改变了运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容,如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。对于整数和指针类型来说,编译器可能对这种额外的工作进行一定的优化;但是对相对复杂的迭代器类型,这种额外的工作就消耗巨大了。建议养成使用前置版本的习惯,这样不仅不需要担心性能的问题,而且更重要的是写出的代码会更符合编程的初衷。
10.递增递减运算符的优先级高于解引用运算符
cout << *iter++ << endl;
11.点运算符和箭头运算符都可用于访问成员
ptr->mem等价于(*ptr).mem
因为解引用运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号。
箭头运算符作用于一个指针类型的对象,结果是一个左值。点运算符分为两种情况,如果成员所属的对象是左值,那么结果是左值;反之,如果成员所属的对象是右值,那么结果是右值。【不明白,WFC】
12.条件运算符(? ,允许把简单的if-else逻辑嵌入到单个表达式中
cond ? expr1 : expr2;
13.        ①位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。
②位运算符(左结合律)
运算符                功能                用法
~                                位求反        ~expr
<<                                左移                expr1 << expr2
>>                                右移                expr1 >> expr2
&                                位与                expr & expr
^                                位异或        expr ^ expr
|                                位或                expr | expr
③关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。
④位求反运算符(~)将运算对象逐位求反后生成一个新值。
⑤如果运算对象是”小整型”,则它的值会据需进行提升成较大的整数类型。
⑥移位运算符(又叫IO运算符)满足左结合律
cout << “hi” << “three” << endl;
等价于
((cout << “hi”) << “three”) << endl;
14.sizeof运算符
①sizeof运算符返回一条表达式或一个类型名字所占的字节数,满足右结合律,所得的值是一个size_t类型的常量表达式。有两种形式:
sizeof(type)
sizeof type
②sizeof返回的是表达式结果类型的大小,并不实际计算其运算对象的值。
Sales_data data, *p;
sizeof(Sales_data);        //存储Sales_data类型对象所占的空间大小
sizeof data;                        //data类型的大小,同上
sizeof p;                                //指针所占大小
sizeof *p;                                //p指向类型的空间大小,同最上
sizeof data.revenue;        //成员对应类型的大小
③因为执行sizeof运算能得到整个数组的大小,可以用数组的大小除以单个元素的大小获得数组中元素的个数:
size_t sz = sizeof(ia) / sizeof(*ia);                //返回ia的元素数量
15.类型转换
①在C++语言中,某些类型之间有关联,如果两种类型有关联,那么当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代。换句话说,如果两种类型可以互相转换(conversion),那么它们就是关联的。
②C++语言不会直接将两个不同类型的值相加,而是先根据类型转换规则将运算对象的类型统一后再求值。此类类型转换是自动执行的,无需程序员介入,因此,被称为隐式转换(implicit conversion)。
③        算术类型之间的隐式转换被设计的尽可能避免损失精度。
整型提升(integral promotion)负责把小整数类型转换成较大的整数类型。
在条件中,非布尔值转换成布尔值。
④如果一个运算对象是无符号类型、另外一个运算对象是带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的,如果此时带符号的恰好位负值,将带来不可预知的副作用。
如果带符号类型大于无符号类型,此时的转换结果依赖于机器。
⑤显式强制转换类型的方法称为强制类型转换(cast)。
16.命名的强制类型转换形式
cast-name <type>(expression)
其中,type是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值,cast-name是static_cast、dynamic_cast、const_cast、reinterpret_cast中的一种。
①static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
int i=1, j=5;
double slope = static_cast<double>(j) / i;
②dynamic_cast
支持运行时类型识别。
③const_cast
只能改变运算对象的底层const。
const char *pc;
char *p = const_char<char*>(pc);
对于将常量对象转换成非常量对象的行为,一般称为”去掉const性质(cast away the const)”。一旦我们去掉了某个对象的const性质,编译器就不再阻止我们进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。
④reinterpret_cast
为运算对象的位模式提供较低层次上的重新解释。
建议:避免强制类型转换。强制类型转换干扰了正常的类型检查,强烈建议程序员避免使用强制类型转换。这个建议对reinterpret_cast尤其适用,因为此类类型转换总是充满了风险。
17.运算符::
功能                                        用法
①全局作用域                        ::name
②类作用域                                class::name
③命名空间作用域                namespace::name
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-10 21:00:54 | 显示全部楼层
2018-01-08【住处断网,后补】
1.控制流(flow-of-control)语句,用以支持比顺序执行更复杂的执行路径。
2.复合语句(compound statement)是指用花括号括起来的(可能为空的)语句和声明的序列,复合语句也被称作块(block)。
如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,则应使用复合语句。
3.当一个if语句嵌套在另一个if语句块内部时,很可能if分支会多于else分支,这个问题通常称作悬垂else(dangling else)。
4.迭代语句通常称为循环,重复执行操作直到满足某个条件才停下来。
5.范围for(range for statement),语法形式:
for(declaration : expression)
statement
expressin必须是一个序列,比如初值值列表、数组、vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员。
vector<int> v={0,1,2,3,4,5,6,7,8,9};
for(auto &r:v)
r *= 2;
等价于
for(auto beg=v.begin(), end=v.end(); beg!=end; ++beg)
{
auto &r=*beg;
r*=2;
}
明白范围for原理,也就明白为什么不能通过范围for语句增加vector对象元素了,范围for语句中,预存了end()的值,一旦在序列中添加(删除)元素,end函数的值就可能变得无效了。
6.        ①break语句负责终止离他最近的while、do while、for、switch语句,并从这些语句之后的第一条语句开始继续执行。
②continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。作用于for、while、do while循环内部。
③goto语句作用是从goto语句无条件跳转到同一函数内的另一条语句。
7.异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。C++中异常处理包括:
①throw表达式(throw expression),异常检测部分使用throw表达式来表示它遇到了无法处理的问题,就说throw引发(raise)了异常。
②try语句块(try block),异常处理部分使用了try语句块处理异常,以关键字try开始,并以一个或多个catch子句(catch clause)结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,他们也被称为异常处理代码(exception handler)。
③一套异常类(exception class),用于在throw表达式和相关的catch子句之间传递异常的具体信息。
8.throw表达式
Sales_item item1, item2;
cin >> item1 >> item2;
if(item1.isbn() == item2.isbn())
{
cout << item1 + item2 << endl;
return 0;
}
else
{
cerr << “Data must refer to same ISBN” << endl;
return -1;
}
可以改写程序使得检查完成后不再直接输出一条信息,而是抛出一个异常:
if(item1.isbn() != item2.isbn())
{
throw runtime_error(“Data must refer to same ISBN”);
}
cout << item1 + item2 << endl;
类型runtime_error是标准库类型的一种,定义在stdexcept头文件中,使用时需初始化runtime_error对象,方式是给它提供一个string对象或者一个C风格的字符串,这个字符串有一些关于异常的辅助信息。
9.try语句块
try
{
program-statements
}
catch(exception-declaration)
{
handler-statements
}
catch(exception-declaration)
{
handler-statements
}
跟在try块之后的是一个或多个catch子句,包括三部分:关键字catch、括号内一个对象的声明(异常声明,exception declaration)以及一个块。当选中了某个catch子句处理异常之后,执行与之对应的块。catch一旦完成,程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。
while(cin >> item1 >> item2)
{
try
{
//执行添加两个Sales_item对象的代码
//如果添加失败,代码抛出一个runtime_error异常
}
catch(runtime_error)
{
cout << err.what() << “\nTry Again?Enter y or n” << endl;
char c;
cin >> c;
if(!cin || c == ‘n’)
break;
}
}
try语句块抛出异常后,沿着程序执行路径逐层回退,知道找到适当类型的catch为止,如果最终还是没有找到匹配catch子句,程序转到名为terminate的标准库函数,该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。
10.标准异常,标准库定义了一组类,用于报告标准库函数遇到的问题。
①exception头文件定义了最通用的异常类exception,它只报告异常的发生,不提供任何额外信息。
②stdexcept头文件定义了几种常用的异常类。
③new头文件定义了bad_alloc异常类型。
④type_info头文件定义了bad_cast异常类型。
我们只能以默认初始化的方式初始化exception、bad_alloc、bad_cast对象,不允许为这些对象提供初始值。其他异常类型的行为则恰好相反,应该使用string对象或者C风格字符串初始化这些类型的对象,但是不允许默认初始化的方式,创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。
11.异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串的const char *。该字符串的目的是提供关于异常的一些文本信息。what函数返回的C风格字符串的内容与异常对象的类型有关,如果异常类型有一个字符串初始值,则what返回该字符串。对于其他无初始值的异常类型,what返回的内容由编译器决定。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-10 21:01:36 | 显示全部楼层
2018-01-09【住处断网,后补】
1.一个典型的函数(function)定义包括以下部分:返回类型(return type)、函数名字、由0个或多个形参(parameter)组成的列表及函数体。
2.通过调用运算符(call operator)来执行函数,它的形式是一对圆括号,作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用都好隔开的实参(argument)列表,用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。
编写函数:        int func(int val){        ;}
调用函数:        int i = func(5);
3.名字有作用域,对象由生命周期(lifetime)。
4.只存在于块执行期间的对象称为自动对象(automatic object)。
5.函数声明也称函数原型(function prototype).
6.定义函数的源文件应该把含有函数声明的头文件包含进来。
7.分离式编译(seperate compilation)允许把程序分割到几个文件中去,每个文件独立编译。
8.当形参是引用类型时,就说它对应的实参被引用传递(passed by reference)或者函数被引用调用(called by reference)。
当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。说这样的实参被值传递(passed by value)或者函数被传值调用(called by value)。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-10 22:54:12 | 显示全部楼层
2018-01-10
1.        int *matrix[10];                //10个指针构成的数组
int (*matrix)[10];                //指向含有10个整数的数组的指针
2.为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,可以编写一种特殊的函数,即可变参数模板。
3.initializer_list形参
①是一种标准库类型,定义在同名头文件中,用于表示某种特定类型的值的数组。
②提供的操作
initializer_list<T> lst;        默认初始化,T类型元素的空列表
initializer_list<T> lst{a, b, c...};        元素数量和初始值一样多,列表中的元素是const
lst2(lst)                拷贝或赋值元素,拷贝后,原始列表和副本共享元
lst2=lst                        素
lst.size()                元素数量
lst.begin()                首元素指针
lst.end()                尾元素下一个位置指针
③void error_msg(initializer_list<string> il)
{
for(auto beg=il.begin(); beg!=il.end(); ++beg)
{
cout << *beg << “”;
cout << endl;
}
}
如果想传递一个值的序列,则必须把序列放在一对花括号内:
if(expected != actual)
{
error_msg({“function”, expected, actual});
}
else
{
error_msg({“function”, “okey”});
}
4.C++还有一种特殊的形参类型(即省略符),可以用它传递可变数量的形参。一般只用于与C函数交互的接口程序。
省略符形参只能出现在形参列表的最后一个位置:
void foo(param_list, ...);
5.函数的返回类型决定函数调用是否是左值,调用一个返回引用的函数得到左值,其他返回类型得到右值。
char &get_val(string &str, string::size_type ix)
{
return str[ix];
}
int main()
{
string s(“a value”);
cout << s << endl;
get_val(s, 0) = ‘A’;
cout << s << endl;
return 0;
}
6.C++新标准规定,函数可以返回花括号包围的值的列表。如前例的error_msg函数:
vector<string> process()
{
if(expected.empty())
{
return{};
}
else if(expected == actual)
{
return{“functionX”, “okay”};
}
else
{
return{“functionX”, expected, actual};
}
}
7.如果函数的返回类型不是void,那么它必须返回一个值,但是有个例外:允许main函数没有return语句直接结束。如果控制到达了main函数的结尾而且没有return语句,编译器将隐式的插入一条返回0的return语句。
8.如果一个函数调用了它自身,不管这种调用是直接调用还是间接调用,都成该函数为递归函数(recursive function)。我们有时候会说这种函数含有递归循环(recursion loop)。注意,main函数不能调用它自己。
9.声明一个返回数组指针的函数
        int arr[10];                //含有10个整数的数组
int *p1[10];        //含有10个指针的数组
int (*p2)[10];        //指针p2指向含有10个整数的数组
和这些声明一样,想定义一个返回数组指针的函数,则数组的维度必须跟在函数名字之后。然而,函数的形参列表也跟在函数名字后面且形参列表应该优于数组的维度。因此,返回数组指针的函数形式如下:
Type (*function(parameter_list))[dimension]
类似于其他数组的声明,Type表示元素的类型,dimension表述数组的大小。(*function(parameter_list))的括号必须存在。
int (*func(int i))[10];
逐层解析:
func(int i)表示调用func函数时需要一个int类型的实参。
(*func(int i))意味着可以对函数调用的结果执行解引用操作。
(*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组。
int (*func(int i))[10]表示数组中的元素是int类型。
10.尾置返回类型(trailing return type),可以用于简化上述func声明。
auto func(int i)->int (*)[10];        //返回一个指针,指向含有10个元素                                                                        //的数组
为了表示函数真正的返回类型跟在形参列表之后,在本应该出现返回类型的地方放置一个auto。
11.也可使用decltype关键字声明返回类型:
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
decltype(odd) *arrptr(int i)
{
return(i%2)? &odd:&even;
}
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-11 23:20:23 | 显示全部楼层
2018-01-11
1.重载(overloaded)函数,在同一个作用域内的几个函数名字相同但形参列表不同。可以在一定程度上减轻程序员起名字、记名字的负担。
void printf(const char *cp);
void printf(const int *beg, const int *end);
void printf(const int a[], size_t size);
不允许两个函数除了返回类型外其他所有的要素都相同。
一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
如果形参时某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时const是底层的。当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。
2.函数匹配(function matching)也叫重载确定(overload resolution),是指函数调用与一组重载函数中的某一个关联起来的过程。
3.函数重载时由三种可能的结果:
①编译器找到一个与实参最佳匹配(best match)的函数,并生成调用该函数的代码。
②找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配(no match)的错误信息。
③有多余一个函数可以匹配,但是每一个都不是明显的最佳选择,此时也将发生错误,称为二义性调用(ambiguous call).
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-12 23:18:41 | 显示全部楼层
2018-01-12
1.三种函数相关的语言特性:
①默认实参(default argument),某些函数的形参在多次调用的时候都被赋予一个相同的值,这个反复出现的值称为默认实参。
一旦某个形参被赋予了默认值,他后面的所有形参都必须有默认值。默认实参负责填补函数调用缺少的尾部实参。
②内联函数(inline),将它在每个调用点上“内联的”展开。
inline const string &shorterstring(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1:s2;
}
cout << shorterstring(s1, s2) << endl;
将在编译过程中展开成类似下面形式:
cout << (s1.size() <= s2.size() ? s1:s2) << endl;
③constexpr函数,是指能够用于常量表达式的函数。
定义constexpr函数的方法与其他函数类似,不过要遵循几项约定:函数的返回值类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。
constexpr int new_sz(); {return 42};
constexpr int foo = new_sz();
允许constexpr函数的返回值并非一个常量。
2.调试帮助,基本思想是,程序可以包含一些用于调试的代码,但是这些代码只在开发程序时使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码。用到两项预处理功能:assert和NDEBUG。
①assert预处理宏(preprocessor macro),是一个预处理变量。使用一个表达式作为条件:
assert(expr);
首先对expr求值,如果为假,assert输出信息并终止程序的执行。如果表达式为真,什么也不做。
assert宏定义在cassert头文件中。预处理名字由预处理器而非编译器管理,因此我们可以直接使用预处理名字而无需提供using声明。也就是说,应该使用assert而不是std::assert,也不需要assert提供using声明。
②DEBUG预处理变量,assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认没有定义NDEBUG。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-13 21:53:25 | 显示全部楼层
2018-01-13
1.函数匹配的第一步时选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。候选函数有两个特征:①与被调用的函数同名;②其声明在调用点可见。
第二步考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数(viable function)。可行函数也有两个特征:①其形参数量与本次调用提供的实参数量相等;②每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。
第三部时从可行函数中选择与本次调用最匹配的函数,基本思想是,实参类型与形参类型越接近,匹配的越好。
2.如果函数含有默认实参,则我们在调用该函数时传入的实参数量可能少于它实际使用的实参数量。
3.调用重载函数时应避免强制类型转换,如果实际应用中确实需要强制类型转换,则说明设计的形参不合理。
4.函数指针指向的是函数而非对象。
bool (*pf)(uint i);
当我们把函数名作为一个值使用时,该函数自动转换成指针。也可以直接使用指向函数的指针调用该函数,无需提前解引用。
5.和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来时函数类型,实际上却是当成指针使用:
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 &));
此时,可以
useBigger(s1, s2, lengthCompare);
6.正如useBigger的声明语句所示,直接使用函数指针类型显得冗长而繁琐,类型别名和decltype能让我们简化使用了函数指针的代码:
//Func和Func2时函数类型
typedef bool Func(const string &, const string &);
typedef decltype(lengthCompare) Func2;
//FuncP和FuncP2是指向函数的指针
typedef bool(*FuncP)(const string &, const string &);
typedef decltype(lengthCompare) *FuncP2;
需要注意的是,decltype返回函数类型,此时不会将函数类型自动转换成指针类型。因为decltype的结果是函数类型,所以只有结果前加上*才能得到指针。可以使用如下形式重新声明useBigger:
void useBigger(const string &,const string &, Func);
void useBigger(const string &,  const string &, FuncP2);
在第一条语句中,编译器自动地将Func表示的函数类型转换成指针。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-14 20:40:06 | 显示全部楼层
2018-01-14
1.类的基本思想是数据抽象(data abstraction)和封装(encapsulation)。
数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程技术。类的接口包括用户所能执行的操作;类的实现包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装实现了类的接口和实现的分离。封装后的类隐藏了实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。
类要想实现数据抽象和封装,需要首先定义一个抽象数据类型(abstract data type)。在抽象数据类型中,由类的设计者负责考虑类的实现过程;使用该类的程序员只需要抽象的思考类型做了什么,无需了解类型的工作细节。
2.尽管所有的成员都必须在类的内部声明,但是成员函数体可以定义在类内也可以定义在类外。类外部定义成员函数时,成员的名字必须包含它所属的类名。
3.成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。
4.构造函数(constructor),一个或几个成员函数来控制其对象的初始化过程。
类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(default constructor)。默认构造函数无需任何实参。
5.如果我们没有显式的定义构造函数,编译器会为我们隐式的定义一个默认构造函数。编译器创建的构造函数又被称为合成的默认构造函数(synthesized default constructor)。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-15 22:49:00 | 显示全部楼层
2018-01-15
1.使用访问说明符(access specifiers)加强类的封装性。
定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。
2.使用struct关键字,定义在第一个访问说明符之前的成员是public的;使用class关键字,这些成员是private的。这是两者定义类的唯一区别。
3.当我们提供一个类内初始值时,必须以符号=或者花括号表示。
4.每个类定义了唯一的类型,对于两个类来说,即使他们的成员完全一样,这两个类也是两个不同的类型。
5.仅声明而不定被称作前向声明(forward declaration)。在声明之后定义之前是不完全类型(incomplete type)。
6.类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数称为它的友元(friend)。如果类想把一个函数作为它的友元,只需要在类内增加一条以friend关键字开头的函数声明语句即可。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-16 23:10:46 | 显示全部楼层
2018-01-16
1.在很多类中,初始化和赋值的区别事关底层效率问题:前者直接初始化数据成员,后者则先初始化再赋值。
2.最好令构造函数初始值的顺序与成员声明的顺序保持一致,而且如果可能的话,尽量避免使用某些成员初始化其他成员。
3.委托构造函数(delegating constructor),使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。
4.聚合类(aggregate class)使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。当一个类满足如下条件时,我们说它是聚合的:
所有成员都是public的;
没有定义任何构造函数;
没有类内初始值;
没有基类,也没有virtual函数。
5.通过在成员的生命之前加上关键字static使得其与类关联在一起。类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。静态成员函数也不与任何对象绑定在一起,他们不包含this指针。
和其他成员函数一样,既可以在类内也可以在类外定义静态成员函数。当在类外定义静态成员时,不能重复static关键字,该关键字只能出现在类内部的声明语句。
因为静态数据成员不属于任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的。而且一般来说,不能在类的内部初始化静态成员。相反的,必须在类的外部定义和初始化每个静态成员。和其他对象一样,一个静态数据成员只能定义一次。
回复

使用道具 举报

14

主题

207

回帖

254

积分

高级会员

积分
254
 楼主| 发表于 2018-1-17 22:00:36 | 显示全部楼层
暂停,休整几日.....
回复

使用道具 举报

36

主题

2037

回帖

2145

积分

至尊会员

积分
2145
发表于 2019-1-11 21:15:05 | 显示全部楼层
精华,刚发现楼主整理的非常好。
Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better.
回复

使用道具 举报

0

主题

30

回帖

30

积分

新手上路

积分
30
发表于 2019-11-13 08:41:22 | 显示全部楼层
hello 想看一下
回复

使用道具 举报

2

主题

31

回帖

37

积分

新手上路

积分
37
发表于 2019-12-9 10:53:13 | 显示全部楼层
我也看看   
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-3-29 20:32 , Processed in 0.373048 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表