您的位置:首页 > 编程语言 > C语言/C++

《C++ Primer 第五版》第1周:C++基础入门(第1章至第3章)

2015-09-06 17:12 477 查看

第1周:C++基础入门(第1章至第3章)

作业:

编写一个实验楼课程管理程序,程序具备下述功能:

程序运行后可以循环输入操作命令

操作命令输入0,打印出程序帮助信息,即每个操作命令的作用

操作命令输入1,打印出程序中存储的所有课程ID和课程名

输入2,打印出课程数量

输入3,打印出名字最长的课程信息,如果有多个相同长度的课程名请都打印出

输入4,删除最后一个课程,如果没有课程需要返回错误信息

输入5,退出程序

chapter 1

1. 函数的定义: return type, function name, parameter list, function body


// int为返回类型,main为函数名,小括号包围的是可以为空的形参列表
int main()
{           // 大括号包围的是函数体
return 0;  // 返回值
}


2. c++程序必须包含一个main函数

3. 源文件命名约定: .cc、.cxx、.cpp、.cp、.c

4. 标准输入输出 iostream: cin, cout, cerr, clog

5. 循环控制语句


// while循环
while (condition) {
statement
}

// 新式for循环
for (init statement; condition; expression) {
statement
}


6. 从键盘输入文件结束符: unix为Ctrl + D,windows为Ctrl + Z

7. 条件控制语句


// if 条件语句
if (condition) {
statement
} else if (condition) {
statement
} else {
statement
}


8. 类:
- 一种用于定义自己的数据结构及其相关操作的机制。
- 每个类都定义了一个新的类型,类型名就是类名。
- 类一般定义在头文件中(.h, .hpp, .hxx)。
- 类的作者定义了类对象可以执行的所有动作。

11. 成员函数是定义为类的一部分的函数(方法)

12. 点运算符 '.' 只能用于类类型的对象:
- 左侧运算对象必须是一个类类型的对象,
- 右侧运算对象必须是该类型的一个成员名,
- 运算结果为右侧运算对象指定的成员。
- 调用运算符'()':括号中为实参列表
- e.g. item.isbn()  // 类类型对象.成员函数(可为空的实参列表)

13. 文件重定向:
- < : 输入重定向
- > : 覆盖输出重定向
- >> : 追加输出重定向

###遇到的问题:

####1. 练习1.23 如果不用map的话只能在输入顺序上要求相同 ISBN的都放一起才能统计准确,所以先使用map完成了


#include <iostream>
#include <map>
#include "Sales_item.h"

using namespace std;

int main()
{
map<string, size_t> stat;
Sales_item item;

while (cin >> item) {
string isbn = item.isbn();
if (stat.find(isbn) == stat.end()) {
stat[isbn] = 0;
}
stat[isbn]++;
}

map<string, size_t>::iterator it;
for (it = stat.begin(); it != stat.end(); it++) {
cout << "ISBN: " << it->first << " " << it->second << endl;
}

return 0;
}


####2. 在编译书里的源代码的时候发现提示了类的声明里不能对变量进行初始化操作:

![make failed](https://dn-simplecloud.qbox.me/uid/undefined/1441177221129.png)

g++ -v看到的版本是4.4.7,尝试着升级gcc到4.5+,整了一个晚上没升级成功,各种依赖的库都缺少。

早上到公司请教了一下同事,说是gcc还依赖于系统的内核版本,没办法只能装了一个centos7的系统。

装完系统之后看到gcc是4.8.3的,重新wget了源码编译了一下就编译成功了。

不过自己写了个例子include了Sales_item.h,编译的时候还是有warning( ** 去掉该警告@chapter2-22 ** ),不过总算可以暂时忽略了:

![编译警告](https://dn-simplecloud.qbox.me/uid/undefined/1441178482760.png)


chapter 2

1. 基本内置类型:算数类型、空类型

2. 算数类型:整型、浮点型

3. short、int、long、long long的区别:存放数据时所占用的内存大小不一样,sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

4. signed和unsigned的区别:signed可以表示正负数或0,unsigned只能表示非负数

5. float和double的区别:float的精度小于double

6. 类型转换可能会导致的问题:溢出、数据截断

7. 避免无法预知以及依赖于实现环境的行为

8. 不要混用signed和unsigned的类型

9. signed 转 unsigned超出范围时会进行取模赋值,如将 -1 赋值给unsigned char类型的变量,则最后的赋值结果是:-1 % 2^sizeof(unsigned char)

10. 字符串默认最后有一个结束符 '\0',所以长度会比字面值的长度多1

11. 变量定义的形式:类型 变量名1, 变量名2;

12. 列表初始化:


// 如果使用列表初始化且初始值存在丢失信息的风险时,编译器会报错
int i = 0;
int i = {0};
int i{0};
int int(0);


13. 变量定义时如果没有初始化,则会被默认初始:
- 函数体外的变量会被初始化为0
- 函数体内的未初始化的变量则不会被默认初始化
- 未被初始化的内置类型变量的值是未定义的
- 类的对象如果没有显示地初始化,则其值由类确定
- 如果试图拷贝或以其他形式访问未定义的值,则会引发错误

14. c++支持分离式编译机制,允许将程序分割为若干个文件,每个文件可被独立编译。为了支持这个机制,c++将声明和定义区分开来

15. 声明和定义:
- 声明使名字为程序所知,一个文件如果想使用别处定义的名字,则必须包含对那个名字的声明。
- 声明规定了变量的类型和名字,这点和定义是相同的,但是仅声明一个变量的话需要在变量名前加上关键字extern,而且不要显示地初始化变量。
- 定义负责创建和名字关联的实体、还会申请存储空间,也可能会为变量赋初始值
- 变量只能被定义一次,但是可以被声明多次
- 任何包含了显示初始化的声明会变成定义,不管是否加了extern
- 在函数体内部初始化一个由extern标记了的变量将会引发错误
e.g.


extern int i; // 声明i而非定义 i
int j;        // 声明并定义 j


16. 作用域:


#include <iostream>

using namespace std;

int reused = 42; // 全局作用域
int main()
{
// 块作用域,声明周期到main函数结束为止
int unique = 0;
// 使用的是全局作用域中的reused
cout << reused << " " << unique << endl;

// 定义一个块作用域内的变量reused,覆盖掉全局作用域中的同名变量
int reused = 0;
// 使用的是块作用域中的reused
cout << reused << " " << unique << endl;

// 显示地访问全局作用域中的reused
cout << ::reused << " " << unique << endl;

return 0;
}


17. 复合类型:引用和指针

18. 引用
- 引用不是对象,只是一个已经存在的对象的别名,所以不能定义引用的引用
- 引用必须被初始化,且初始值必须是一个对象(非字面值,常量引用可以使用字面值初始化),初始化之后无法被重新与其他的对象进行绑定
- 一般情况下引用的类型需要与绑定的对象的类型一致,两种例外情况如下:
- 引用的类型与对象的类型存在继承关系:SubClass sub; Parent &p1 = sub;
- 引用的类型与对象的类型含有一个可接受的const类型转换规则:


/**
* 下面的代码编译器会将其优化为:
* double dval = 3.14;
* const int temp = dval;
* const int &ri = temp;
* 实际使用中避免这种情况。
*/
double dval = 3.14;
const int &ri = dval;


19. 指针,建议初始化所有的指针
- 指针本身是一个对象,允许指针赋值和拷贝
- 指针不要求在定义时就初始化
- 函数体内的指针未初始化时的值也是未定义的
- 指针存放的是指定对象的地址,获取对象的地址可以使用取地址符 &
- 一般情况下引用的类型需要与绑定的对象的类型一致,两种例外情况与引用的相同
- 无效指针(野指针):值是未定义的指针
- 空指针:没有指向任何对象的指针,生成空指针的方法:


int *p = nullptr;
int *p = 0;
// #include cstdlib
int *p = NULL;


- 通过解引用符 * 来利用指针访问对象,给解引用的结果赋值等价于给指针所指的对象赋值
- 非0的指针对应的条件值都是true
- void*指针可以存放任意对象的地址,但是因为无法确定void*所指向的对象是什么类型以及可以对该对象进行什么操作,所以不能直接操作void*所指向的对象
- 二级指针:指向指针的指针
- 引用不是对象,所以不能定义一个指向引用的指针

20. const限定符
- const限定的变量不能进行更改,所以必须初始化
- 默认状态下,const对象仅在文件内有效,编译器会将const对象替换成初始化时的值,如果要在多个文件共享该对象,则需要用到extern
- 常量引用可以指向常量引用与非常量引用,但是非常量引用不能指向常量引用,常量引用不能重新赋值,常量引用可以使用字面值进行初始化
- 常量指针可以指向常量对象与非常量对象,但是非常量指针不能指向常量对象,常量指针可以重新赋值,但是不能通过常量指针修改其指向的对象

21. 类型别名


typedef double my_double; // mydouble md = 3.14159;
type double *my_dp; // my_dp dp = &md;
using SI = Sales_item; // SI book;


22. auto类型
- auto i = 1; 编译器会自动分析推断 i 的类型
- auto定义的变量必须要初始化
- auto在一条语句上声明多个变量时,该语句的所有变量的初始基本数据类型都必须一样:auto i = 0, b = 1;
- 编译是需要加上--std=c++0x选项才能支持c++11的特性,如:g++ --std=c++0x -g auto.cpp -O0 -o auto,否则会提示error: 'i' does not name a type


#include <iostream>

int main()
{
const int i = 1;
// j 是int,并不是const int
auto j = i;
// k 是const int
const auto k = j;
// m 是const int*,而不是int*
auto m = &i;
// n 是const int&,而不是int&
auto &n = i;

return 0;
}
/**
* gdb调试过程:
* > (gdb) b main
* > (gdb) r
* > 7        const int i = 1;
* > (gdb) n
* > 8        auto j = i;
* > (gdb)
* > 9        const auto k = j;
* > (gdb)
* > 10       auto m = &i;
* > (gdb)
* > 11       auto &n = i;
* > (gdb)
* > 13       return 0;
* > (gdb) p &i
* > $1 = (const int *) 0x7fffffffe434
* > (gdb) p &j
* > $2 = (int *) 0x7fffffffe44c
* > (gdb) p &k
* > $3 = (const int *) 0x7fffffffe448
* > (gdb) p m
* > $4 = (const int *) 0x7fffffffe434
* > (gdb) p &n
* > $5 = (const int *) 0x7fffffffe434
*/


23. 自定义数据结构


struct A {
// 类体
} variable1, variable2;

// 等价于:
struct A {
// 类体
};
A variable1, variable2;


24. 文件头保护符:#ifndef-#endif,#ifdef-#endif,无视作用域的规则,必须唯一

###练习2.14


int i = 100, sum = 0;

// 定义一个块作用域内的变量i,覆盖掉上一级的作用域中的同名变量
// 此变量i的声明周期到for循环结束为止
for (int i = 0; i != 10; i++) {
sum += i;
}

// 这个i处于for循环作用域之外,属于当前作用域,所以输出的结果为:100 45
cout << i << " " << sum << endl;


###练习2.3


#include <iostream>
#include <limits>

using namespace std;

int main()
{
unsigned int u = 10;
unsigned int u2 = 42;
unsigned int  max_unsigned_int = numeric_limits<unsigned int>::max();

cout << "expected: u2 - u = 32" << endl;
cout << "result:   u2 - u = " << u2 - u << endl << endl;

cout << "expected: u - u2 = (u - u2) % 2^sizeof(unsigned int) = " << (u - u2) % max_unsigned_int << endl;
cout << "result:   u - u2 = " << u - u2 << endl << endl;

int i = 10;
int i2 = 42;

cout << "expected: i2 - i = 32" << endl;
cout << "result:   i2 - i = " << i2 - i << endl << endl;

cout << "expected: i - i2 = -32" << endl;
cout << "result:   i - i2 = " << i - i2 << endl << endl;

cout << "expected: i - u = (i - u) % 2^sizeof(unsigned int) = " << (i - u) % max_unsigned_int << endl;
cout << "result:   i - u = " << i - u << endl << endl;

cout << "expected: u - i = (u - i) % 2^sizeof(unsigned int) = " << (u - i) % max_unsigned_int << endl;
cout << "result:   u - i = " << u - i << endl;

return 0;
}


编译、执行的输出结果为:


[lovecat@localhost chapter2]$ g++ -g 2.3_convert.cpp -O0 -o 2.3_convert

[lovecat@localhost chapter2]$ ./2.3_convert

expected: u2 - u = 32
result:   u2 - u = 32

expected: u - u2 = (u - u2) % 2^sizeof(unsigned int) = 4294967264
result:   u - u2 = 4294967264

expected: i2 - i = 32
result:   i2 - i = 32

expected: i - i2 = -32
result:   i - i2 = -32

expected: i - u = (i - u) % 2^sizeof(unsigned int) = 0
result:   i - u = 0

expected: u - i = (u - i) % 2^sizeof(unsigned int) = 0
result:   u - i = 0


chapter 3

1. 头文件不应包含using声明

2. 初始化string对象:


string s1;           // 默认初始化,s1是空串
string s2(s1);       // s2是s1的副本,直接初始化
string s2 = s1;      // 同上,拷贝初始化
string s3("value");  // s3是字面值"value"的副本,除了字面值最后的 '\0'外
string s3 = "value"; // 同上
string s4(n, 'c');   // 把s4初始化为连续 n 个字符 'c' 组成的字符串,直接初始化


3. 读取未知数量的string对象


#include <iostream>
#include <string>

using namespace std;

int main()
{
string str;
// 逐个读取单词,直到文件末尾
while (cin >> str) { // 逐行读取,保留输入的空格:while (getline(str))
cout << str << endl;
}

return 0;
}


4. "string".size()的返回值是string::size_type类型

5. 字符串字面值与string不是同种类型

6. range for,c++11的语句,编译时需加选项 --std=c++0x,其语句体内不应该改变其遍历序列(string, vector, iterator等)的大小


#include <iostream>
#include <string>

using namespace std;

int main()
{
string str = "asdfg";
for (char c : str) {
cout << c << endl;
}

return 0;
}


7. 下标运算符 [],接收的是string::size_type类型参数,返回值是该位置上的字符的引用。不可以使用[]越界访问

8. 类模板:提供一些额外的信息来指定模板到底实例化成什么样的类,需要提供哪些信息由模板决定,提供信息的方式:template_name<T>

9. vector对象的初始化:


vector<T> v1;            // 默认初始化,v1是一个空的vector,它潜在的元素是T类型的
vector<T> v2 {t1, t2};   // v2包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v2 = {t1, t2}; // 同上
vector<T> v3 = (n);      // v3包含了n个重复地执行了值初始化的对象
// 余下的方式与string的初始化方式类似


10. vector、string对象的下标运算符可用于访问已存在的元素,而不能用于添加元素

11. 迭代器
- 运算符与指针相似(解引用、迭代器的移动等)
- 如果容器为空,则begin和end返回的都是尾后迭代器
- 因为尾后迭代器实际上不是指向某个元素,所以不能对其进行操作
- 指针也是迭代器

12. 箭头运算符:把解引用和成员访问两个操作结合在一起,如 it->mem等价于(*it).mem

13. array和vector:
- array和vector同样是存放类型相同的对的容器
- array和vector同样只能存放对象,不能存放引用
- array和vector的初始化方式类似
- array的长度确定不变,不能随意增加元素,而vector的长度会根据元素的增加自动增长
- vector的灵活性会好一点,但是对某些特殊的应用来说使用数组类型程序的运行时性能较好

14. 在函数内部定义某种内置类型的array时,默认初始化会令数组含有未定义的值

15. array不允许拷贝内容给其他数组进行初始化或者赋值,编译器有可能自动地将数组名字转换为一个指向数组首元素的指针

16. 避免使用c风格字符串和c标准库string函数,容易引发程序漏洞

17. 多维数组:数组的数组,嵌套数组

###练习3.26:begin和end拿到的都是地址,所以只能通过偏移量去获取middle,否则拿到的就是一个未知的地址

###练习3.36:
+ vector当且仅当他们的元素数量相同且对应的位置的元素也一致时才会相等
+ array对比时是使用的数组名进行判断,编译器自动转换为指向相应数组的首元素的指针的比较


#include <iostream>
#include <vector>

using namespace std;

int main()
{
int a1[] = {1, 2, 3};
int a2[] = {1, 2, 3};
vector<int> v1 {1, 2, 3};
vector<int> v2 {1, 2, 3};

if (a1 == a2) { // &a1[0] != &a2[0],所以对比结果 a1 != a2
cout << "a1 == a2" << endl;
} else {
cout << "a1 != a2" << endl;
}

if (v1 == v2) { // v1和v2的元素数量以及对应的位置的元素都一致,所以对比结果 v1 == v2
cout << "v1 == v2" << endl;
} else {
cout << "v1 != v2" << endl;
}

return 0;
}


+ 编译、执行的结果:


a1 != a2
v1 == v2


Week 1 作业

*Course_mgmt.hpp *


/**
* Course_mgmt.hpp
*/
#ifndef COURSE_MGMT_HPP
#define COURSE_MGMT_HPP

//#include <iostream>

struct Course {
int id;           // course id
std::string name; // course name
};

#endif


*Course_mgmt.cpp *


/**
* Course_mgmt.cpp
*/
#include <iostream>
#include <vector>
#include "Course_mgmt.hpp"

using namespace std;

// error code
#define SUCCESS    0
#define NO_COURSE -1

// options
#define OPTION_HELP_MESSAGE                0
#define OPTION_SHOW_COURSE_BASE_INFO       1
#define OPTION_SHOW_COURSE_AMOUNT          2
#define OPTION_SHOW_LONGEST_COURSE_NAME    3
#define OPTION_DELETE_LAST_COURSE          4
#define OPTION_EXIT                        5

// the id of course which will be increased.
int course_id_counter = 100;
int generate_course_id()
{
return course_id_counter++;
}

vector<Course> initialize()
{
vector <Course> courses;=====--dd
string names[] = {"Linux", "C++", "HTML", "HTML5", "NodeJS", "Shell", "Python"};

for (string name : names) {
Course course_tmp;
course_tmp.id =  generate_course_id();
course_tmp.name = name;

courses.push_back(course_tmp);
}

return courses;
}

// option 0
void show_help()
{
string usage = "\nEnter the option number follow by:\n";
usage += "0 show help message\n";
usage += "1 show courses base info\n";
usage += "2 show courses amount\n";
usage += "3 show the longest course name\n";
usage += "4 delete the last course\n";
usage += "5 exit\n";

cout << usage << endl;
}

// option 1
void show_course_base_info(vector<Course> courses)
{
if (courses.empty()) {
cout << "Empty courses!" << endl << endl;
return;
}

vector<Course>::iterator it;
cout << "ID" << "\t"  << "COURSE" << endl;
for (it = courses.begin(); it != courses.end(); it++) {
cout << it->id << "\t"  << it->name << endl;
}

cout << endl;
}

// option 2
void show_course_amount(vector<Course> courses)
{
vector<Course>::size_type amount = courses.size();
cout << "courses amount: " << amount << endl << endl;
}

// option 3
void show_longest_course_name(vector<Course> courses)
{
if (courses.empty()) {
cout << "Empty courses!" << endl << endl;
return;
}

vector<Course>::size_type longest_size = courses.at(0).name.size();

vector<Course>::iterator it;
for (it = courses.begin(); it != courses.end(); it++) {
if (it->name.size() > longest_size) {
longest_size = it->name.size();
}
}

cout << "the longest course name: " << endl;
for (it = courses.begin(); it != courses.end(); it++) {
if (it->name.size() == longest_size) {
cout << it->name << endl;
}
}

cout << endl;
}

// option 4
int delete_last_course(vector<Course> &courses)
{
if (courses.empty()) {
cout << "delete last course failed, empty courses!" << endl << endl;
return NO_COURSE;
}

vector<Course>::size_type last_index = courses.size() - 1;
string last_course_name = courses.at(last_index).name;

courses.erase(courses.end() - 1);

cout << "delete last course[" << last_course_name << "] success" << endl << endl;
return SUCCESS;
}

int main()
{
// initialize all courses
vector<Course> courses = initialize();

show_help();
cout << "Please Enter: " << endl;

int option;
while (cin >> option) {
if (option == OPTION_HELP_MESSAGE) {
// show help message
show_help();
} else if (option == OPTION_SHOW_COURSE_BASE_INFO) {
// show courses id and name
show_course_base_info(courses);
} else if (option == OPTION_SHOW_COURSE_AMOUNT) {
// show course amount
show_course_amount(courses);
} else if (option == OPTION_SHOW_LONGEST_COURSE_NAME) {
// show the longest course name
show_longest_course_name(courses);
} else if (option == OPTION_DELETE_LAST_COURSE) {
// delete the last course
int code = delete_last_course(courses);
if (code == SUCCESS) {
cout << "the rest courses: " << endl;
show_course_base_info(courses);
}
} else if (option == OPTION_EXIT) {
// exit this cycle
cout << "Exit!" << endl;
break;
} else {
cout << "Wrong Input!" << endl;
// show help message
show_help();
}

cout << "Please Enter: " << endl;
}

return SUCCESS;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ primer