第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;
}