C++Primer学习笔记Part 1

第二章

2.1 基本内置类型
  1. 明确知道变量不可能为负数时,选用无符号
  2. 整形变量一般选用int, 长一点的选用long long.浮点型变量选用double,与float相比,精度更高并且计算代价相差不大。
  3. char 和 bool不要应用于算术表达式,一些机器上char是无符号,一些是有符号,所以如果需要使用一个不大的整数,指定unsigned char或者signed char.
  4. unsigned 和 signed 不要一起用,最后的结果会转换为无符号,如果结果为负,坏菜了。
2.2变量

1.理解初始化和赋值的区别,初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。个人理解就是初始化之前的对象是没有值的。为了避免程序的未知错误,建议初始化每一个变量值,我知道string是不用初始化的。

2.3复合类型

指针与引用的区别:指针是一个对象,引用不是对象,只是对象的另一个名字。引用必须初始化并且始终绑定一个对象。&和*放在等式左边意味着变量类型,起修饰作用,放在等式右边意味着取地址和解引用(取值)。
两种定义指针的方法:二者没有对错之分,熟悉哪个就坚持用哪个。

1 int *p1, *p2;
2 int* p1;
  int* p2;
2.4 const限定符

const变量的值不能被改变,所以const对象必须初始化。如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。常量表达式是指值不会改变并且编译过程就能得到计算结果的表达式。如果认定一个变量是常量表达式,那就把它声明成constexpr类型以便由编译器来验证变量的值是否为常量表达式。值得注意的是:constexpr int *q=nullptr;//q是一个指向整数的常量指针 相比于 const int *q = nullptr;//q指向一个整形常量

const指针
const double pi = 3.14;
double *ptr = π     //错误:ptr不是常量指针,有可能会改变pi的值,而pi的值是不能被改变的
const double *ptr = π //正确

const double *const p = π 秘诀在于从右向左看,p代表变量名,const代表p为常量,*代表p是指针,double代表指针指向double类型的对象,const修饰double,代表指向的double类型的对象是常量。
2.5处理类型
1. typedef
typedef double wages;    //wages等同于double
typedef wages base,*p;   //p是指向double的指针,注意等式左边的*起修饰作用
const p ptr = 0;         //意味着ptr也是指向double的指针。注意不能替换为const double* ptr = 0;前者含义是常量指针, 后者是指向double常量的指针;

2. auto
auto定义的变量必须有初始值,并且同一语句中定义的类型要相同,注意const intint视为不同的类型。
2.6自定义数据结构

定义自己的头文件:
为了确保各个源文件中类的定义一致,类通常被定义在头文件中。头文件通常包含那些只能被定义一次的实体,如类,const, constexpr.头文件也经常用到其他头文件的功能。
预处理器概述:
确保头文件多次包含仍能安全工作的常用技术是预处理器。预处理器是编译之前执行的一段程序。其中一项功能是#include ,当预处理器遇到它,会用指定的头文件内容代替#include.还有一项预处理功能是头文件保护符,#define指令把一个名字设置为预处理变量,而它有两种状态,已定义和未定义。#ifdef只有当变量已定义为真,#ifndef只有当变量未定义时为真。一旦检查结果为真,执行后续操作直到遇到#endif指令为止。使用这些功能能有效避免重复包含。

#ifndef SALES_DATA_H 
#define SALES_DATA_H
#include <string>
struct Sales_data{
    std::string bookNo;
    unsigned unit_sold = 0;
    double revenue = 0.0;
};  //注意类后的分号
#endif

说明: 第一次包含Sales_data.h时,执行操作致#endif,Sales_data.h也会被拷贝到程序中来;第二次包含时,由于SALES_DATA_H变为已定义,编译器将忽略#idndef至#endif之间的部分。整个程序中的预处理变量(包括头文件保护符,例如SALES_DATA_H)必须唯一,通常的做法是基于头文件中类的名字来构建保护符的名字,为了避免与程序中其它实体发生名字冲突,一般把预处理变量的名字全部大写。值得注意的是,即使头文件目前还没被包含在任何其他头文件中,也应该设置保护符。

第三章 字符串、向量、数组

3.1 命名空间的using声明

头文件不应包括using声明,因为include会将头文件拷贝到源文件,可能会造成冲突;

3.2 string
  1. string::size_type 是一种描述string对象大小的类型,显然是无符号的,所有的size()函数返回此类型。如有size_type就不要再用int,无符号和有符号一起的表达式时,有符号会变为无符号,结果会不准。
  2. 两个string对象的比较: ==、!=、<、>、+(+号两边的对象有一个需要时string,”hello”并不是string对象)
  3. cctpe中的函数: isalnum(c)-当c是字母或数字为真;类似的是大小写字母、数字、控制字符、标点符号;c++程序中include cname意味着是c++的头文件,name.h意味着是c的头文件,尽量用前者;
  4. string s(lee song yu);for (auto &c :s) c = toupper(c);//c是字符的引用,才能改变s;toupper为将字符变成大写。 //注意: for (a:all) 是范围for循环
3.3 vector
  1. vector的初始化: vector v(10, 42); //代表着有10个值,每个值是42; 列表初始化vector v= {1,2,3,4,5} or vector v{1,2,3,4,5};值得一提的是,vector可以高效的动态添加,所以一开始不指定初始值是个很好的选择。除非初始值都相同。
  2. 范围for语句体内不能改变其所遍历序列的大小
    vector<int> v(10,1); for(auto i:v) v.push_back(1); //错误
3.4 迭代器
  1. 容器为空,v.begin()和v.end()返回的是同一值,end也被称为尾后迭代器,所以可以用v.begin()==v.end()判断容器是否为空。
  2. 迭代器两种类型,iterator、const_iterator,后者及不能改变容器的值。c++11标准cbegin(),cend()返回的迭代器类型就是后者。但一般可以直接指定 auto i = v.begin();
  3. *iter返回的是iter所指元素的引用;箭头运算符简化了it->mem 和(*it).mem等价
  4. 凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
  5. 迭代器运算,不能超过end,+,-,<,>.
3.5 数组
  1. 初始化必须指定大小,即元素个数必须为常量表达式;例如 unsigned num =10;int a[num];//错误,num不是常量表达式;或者用列表初始化法:int a[]={1,2,3,4,5}; 不能用一个数组去初始化另一个数组,对比的是vector可以,vector<int> v(v1);char类型的数组可以用 char c[]="song";来初始化,但此时字符串末尾的空字符也被复制到数组c里,也就是说数组c的大小事5;
  2. c++11新标准,int a[]={1,2,3,4,5}; int *p = begin(a); int *p1 = end(a);//p代表指向数组第一个元素的指针,p1代表指向数组最后一个位置的下一个位置的指针。与迭代器中begin和end用法相似。 使用数组初始化vector, vector<int> v(begin(a), end(a));//只需指明拷贝区域的首元素地址和尾后地址 vector<int> v(a+1, a+4); //指针运算不要超过数组的最后一个位置的下一个位置
  3. int *p[10];//含有10个整形指针的数组 int (*p)[10];//p指向一个含有10个整形变量的数组 int(&a)[10];//a是一个含有10个整形变量数组的引用
  4. 内置的下标运算符的索引值不是无符号类型,与vector和string不同,即内标访问可以为负。
  5. 尽量不用C风格字符串,代为使用string,安全、高效。
3.6 多维数组
  1. int a[3][4]; 第一个数字代表数组大小,第二个数字代表元素大小;这好似一堵墙;二维数组称为行、列。 int arr[2][3][4]; 这好比一个立方体;
  2. int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; 初始化值。 int a[3][4] = {{0},{4},{8}}; 初始化每行的第一个元素; int a[3][4] = {0,1,2,3};初始化第一行。
  3. 范围for语句处理多维数组int ia[rowCnt][colCnt]; size_t cnt =0; for(auto &row : ia) for(auto &col : row =) col = cnt; cnt++;// 值得注意的是,用引用不仅是因为要修改数组元素的值,更是为了避免数组自动转化为指针;例如 for(auto row:ia) 此时row退化成指向该数组首元素的指针。所以,利用范围for语句处理多维数组,除了最内层的嵌套,其它都应该是引用。
  4. auto 来访问多维数组;
方式1
int ia[3][4];
for(auto p=ia;p!=ia+3;++p){    //ia退化成指向数组首元素的指针,p指向一个含有4个整形元素的数组
    for(auto q = *p;q!=*p+4;++q)      //*p是一个含有4个整数的数组(*p代表p所指向的东西),*p退化成指向数组的首元素的指针
       cout<<*q<<' ';
    cout<<endl;
}
方式2
for(auto p=begin(ia);p!=end(ia);++p){
    for(auto q = begin(*p);q!=end(*p);++q)
        cout<<*q<<' ';
    cout<<endl;
}

第四章 表达式

  1. -m%n = -(m%n) ; m%(-n) = m%n;
  2. 短路求值: 逻辑与&& 逻辑或, 左侧运算对象是为了确保右侧对象求值过程的正确性和安全性。
  3. 赋值满足右结合律: a = b =0; b=0, a =b;
  4. 赋值运算符的优先级低于关系运算符,在条件语句中,赋值部分通常加上括号;if((i=v.getvalue())!=42)
  5. 除非必须,否则不用递增的后置版本,因为后置版本还要再返回原始值,建议养成前置版本的习惯。如果想在复合表达式中既将变量加1减1又能使用它原来的值,就可以用后置版本。
auto p = v.begin();
while(p!=v.end()&& *p>=0)
   cout<<*p++<<endl;         //++的优先级高于解引用,所以等价于*(p++),先运行后置,将p的值加1,但返回的是p的原始值。

6 p->mem 等价于 (*p).mem

string s = "song", *p = &s;
auto n =(*p).size();   //二者等价于s.size()
n = p->size();

7 条件运算符的优先级很低,当长表达式内嵌套了条件运算符,在两边加上括号。还可以嵌套条件运算符

string final = (grade>90)?"high pass":(grade<60)?"fail":"pass";

8 位运算符作用于整形,如果运算对象是小整形,它的值会自动被提升。char类型进行位运算会被提升为int类型。关于符号位没有明确的定义,所以强烈建议将位运算作用于无符号类型。
9 对数组执行sizeof运算得到整个数组所占空间的大小,所以可用sizeof(ia)/sizeof(*a)求数组大小。
10 逗号运算符,表达式1,表达式2.先求解表达式1,再求解表达式2,最后返回表达式2的值。常用于for循环。将size到1的值赋给v, 其实也就是并列执行。逗号的优先级最低。

vector<int>::size_type cnt = v.size();
for(vector<int>::size_type i =0;i!=cnt;++ix,--cnt)
   v[i] =cnt;

11 类型转换, double fval; bool flag; flag = fval; 如果fval为0,flag为false,其他的值为true.

第五章 语句

  1. 空语句: 语法上需要,逻辑上不需要,用一个;代表。
while(cin>>s)
   ;

2 switch语句中,几个case写在一起也可以,代表的某个范围的值

switch(ch)
{
  case 'a':case'e':case 'i':case 'o':
     ++vowelCnt;
     break;
}

3 for循环中的初始化表达式可以有多个值,但类型要相同.可以省略for()中的任意一个部分,但要记得写分号。

for(decltype(v.size()) i=0, sz=v.size();i!=sz;++i)
   v.push_back(v[i]);

4 do while和while类似,区别在于至少执行一次循环。
5 异常处理,throw表达式,try语句块,try关键字开始,一个或多个catch语句结束。

第六章 函数

6.1基础
  1. 静态局部对象:在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止时才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。但它的作用域是局部的!
// 统计函数被调用次数
size_t count_calls()
{
  static size_t ctr = 0;  //如果没有显示初始化,默认为0
  return ++ctr;
}
int main()
{
  for(size_t i=0;i!=10;++i)
    cout<<count_calls()<<endl;  //即使每一次结束函数,ctr的值仍被保留
}
  1. 分离式编译: 在头文件中进行函数声明(采用预编译方式),含有函数声明的头文件应该被包含到定义函数的源文件中。注意源文件都要包含头文件!
    项目文件
    头文件实现函数声明
    源文件实现函数定义
    main
6.2 参数传递
  1. 拷贝大的类类型对象或者容器对象比较低效,使用引用形参避免拷贝比较明智。并且如果函数无须改变引用形参的值,最好将其声明为常量引用。并且使用引用形参可以返回另外的值。因为函数只有一个返回值。
  2. const 形参:当用实参初始化形参时会忽略到顶层constvoid f(const int i){} int a =10; f(a) 不会报错,如果是const int& 那么必须传入的是常量。如果不改变实参,尽量使用常量引用,这是一种好的设计,可以避免错误。
  3. 数组形参: 数组的两个特性,不允许拷贝数组、使用数组时会将其转化成指针。以数组作为形参要确保数组不会越界。3钟方法。
//1.数组是C风格字符串
void print(const char *cp)
{
  if(cp){
  while(*cp)
    cout<<*cp;
  }
}
// 使用标准库
void print(const int*beg, const int *end)
{
  while(beg!=end)
     cout<<*beg++;
}
int j[2] ={1,2};
print(begin(j), end(j));
//显式传入一个表示数组大小的形参, int ia[] 与 int *ia等价 与 int ia[10]
void print(const int ia[], size_t size);
//传递多维数组, 注意括号不能省略,否则变为包含10int指针的数组
void print(int *matrix[][10], int rowsize);
void print(int (*matrix)[10], int rowsize);
  1. 可变形参的函数 :initializer_list
6.3 返回值
  1. 函数完成后,它所占用的存储空间也被释放掉,不要返回局部变量的引用或指针。
  2. 列表初始化返回值vector<string> process() return {"li", "song","rain"};
  3. 返回数组指针: 定义类型别名,typedef int arrT[10]; using arrT = int[10]
    直接返回 int(*func(int i))[10] func的参数是int, 返回值是数组指针
6.4 函数重载
  1. 函数名字相同但形参列表不同。返回值不同并不是函数重载。另外:int f(int i); int f(const int i) 为一个函数,顶层const不影响传入函数的对象。但是形参如果是某种类型的指针或引用,即重载int f(int &i); int f(const int &i); const_cast可将非常量与常量互相强制转换。const_cast
6.5 函数指针
  1. 函数指针指向了某一类函数,返回值,参数相同的函数(与函数名无关)。将函数指针作为参数时,可指向这一类函数。
  2. bool lengthCompare (const string& s1, const string& s2); 对应的函数指针是bool(*fp)(const string&, const string&);//未初始化,fp两端的()必不可少 初始化可以fp=0;fp=nullptr;
  3. 当把函数名作为一个值使用时,该函数自动转化成指针。将lengthcompare的地址赋给fpfp=lengthcompare; fp=&lengthcompare;//二者等价,如果等式右侧的类型(返回值,参数)与函数指针不匹配会报错 ,也可以直接调用bool a=fp("song","rain"); bool a=(*fp)("song","rain");//二者等价
  4. 函数指针作为形参:void useBigger(const string &s1, const string &s2, bool fp(const string&, const string&)); void useBigger(const string &s1, const string &s2, bool (*fp)(const string&, const string&));//二者等价 调用此函数时useBigger(s1,s2,lengthcompare);
    简化版本的两种方式:
//Func, Func2函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lenthcompare) Func2;
//FuncP, FuncP2是指向函数的指针
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lenthcompare) *FuncP2; //decltype只是返回函数类型,要在FuncP2前加*才代表指针
//函数的使用,二者等价
useBigger(s1,s2,Func);  //编译器自动将Func表示的函数类型转换为指针
useBigger(s1,s2,FuncP2);

5 返回指向函数的指针:使用类型别名简化using pF = int(*)(int*, int);//注意必须有() pF f1(int); 也可以直接声明:int(*f1(int))(int*, int)// f1是函数名,参数为int,返回一个函数指针 最后如果明确知道返回的函数是哪一个,可用decltype简化函数指针返回值

size_type sumLength(const string&, const string&);
decltype(sumLength) *getF(const string&);//decltype只是返回函数类型, 加*代表返回的是函数指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值