C++primer笔记

待解决

const_cast常量转换

constexpr函数

变量

声明与定义 extern

c++支持分离式编译,声明说明变量的类型和名字,而定义申请存储空间(或赋初始值)。
extern关键字在多个模块之间共享变量时很有用,在某个模块里定义,在另一个模块中用extern声明即可。
在file1.cpp

int global_int = 1;

在file2.cpp

extern int global_int;
//in some function
cout << "global_int = " << global_int;

注:如果变量是const常量,无论定义还是声明,都需要加extern。原因在于const默认作用域仅在文件内有效,需要扩大。
声明可以有多次,定义只能有一次

引用

对象是指占有一块内存区域,具有某种类型。引用不属于对象,只是变量的别名。一旦定义引用,无法将其绑定到其他对象,且引用定义必须初始化。

constexpr 变量定义常量

constexpr用于声明常量,用该关键字声明的变量一定是常量。

顶层const

指针本身是一个对象,它可以指向另一个对象。 名词顶层const表示指针本身是一个常量,底层const表示指针所指向的对象是一个常量。

const int *p;//p是顶层const, 
int * const p;//p是底层const,不能给p赋值

decltype 自动类型推断

decltype作用是选择并返回操作数的数据类型。
模板函数中未知变量类型声明 关键字decltype


template <class T1, class T2>
void ft(T1 x, T2 y)
{
	decltype(x+y) xpy = x + y;
}


int x;
decltype (x) y;//y类型与x相同
decltype 作用于函数时,返回函数类型而不是函数指针;
decltype作用于数组时,返回数组类型而不是数组首元素的指针;

字符 向量 数组

string

初始化
string s1;//默认空字符串
string s2(s1);
string s2 = s1;
string s3("value");
string s4 = "value";
string s5(n,'c');//s5为长度为n,每个元素都为c的字符串  
string::size_type

string和其他标准库定义了几种配套的类型,类型size_type就是,其为size()函数返回的类型

auto len = line.size();//len 类型为string::size_type

此外,不要混用无符号类型和有符号类型

unsigned int int a = 1;
 int b = -1;
 cout << (a<b) <<endl;
//结果输出 1 ,因为负值会自动转化成一个较大的无符号值
string用于相加

两个string对象相加没问题,string与字面值相加,其中至少有一个为string对象,即加号两边至少有一个为string对象

string s1;
string s2 = s1 + "abc" + "def";//没问题 
//等价于string temp = s1+ "abc"; string s2 = temp + "def";
string s3 = "abc" + "def" + s1;//error,加号两边至少有一个为string对象
string与for循环相结合

若要改变string里面的字符,需要用引用

string s("hello world");
for(auto &c :s)//引用
	c = toupper(c);//变为大写

vector

{}初始化

默认情况下,()用于vector初始化,里面内容不是作为元素,通常带个数;{}里面内容通常作为元素进行初始化。如下:

vector<int > v1(10);//10个元素,每个都是0
vector<int> v2{10};//1个元素,值为10

vector<int > v3(10,1);//10个元素,每个都是1
vector<int> v2{10,1};//2个元素,值为10和1

当{}默认失败时,提供类似()的初始化功能,如:

vector<string>v7(10);//v7有10个默认初始化的元素
vector<string>v7{10};//v7有10个默认初始化的元素
for循环

for循环中,不应改变序列的大小。如vector用于for 循环,如果插入元素,原本存的end()迭代器会失效。

vector<int > v = {1,2,3,4,5,6};
for(auto &i:v)
	i = i + 1;

不能以下标方式添加vetor元素。

数组

数组与auto decltype

auto a(数组名),a为指针; decltype b(数组名), b为数组

int ia[] = {1,2,3,4,5};
auto a(ia);
a = 42l//error a为指针
decltype(ia) b = {1,2,3,4,5};
b[3] = 4;
begin(数组名) end(数组名)

不同于标准库的 变量.begin(),变量.end(),数组的begin,end是数组作为参数

int ia[] = {1,2,3,4,5,6};
int *beg = begin(ia);
int *last = end(ia);//数组末元素的下一个元素
数组初始化vector对象
int arr[] = {1,2,3,4,5};
vector<int> ivec(begin(arr), end(arr));
vector<int> ivec(arr+1, arr+4);//ivec初始化为arr[1],arr[2],arr[3]
多维数组for循环

for语句处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。这是为了避免数组被自动转化为指针。

int arr[3][4] = {1,2,3,4,5,6,7,8,9,10};
for(auto row:arr)//row被转化为指针,不能被col用for遍历,编译不通过
	for(auto col:row)
			cout << col;
//编译不通过

表达式

左值和右值

当一个对象被用作右值时,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存的位置)。
使用关键字delctype时,如果表达式求值结果是左值,作用于该表达式得到一个引用类型;由于取地址运算符生成右值,所以delctype(&p)结果是int **, 是一个指向整数指针的指针。
算数运算符的运算对象和求值结果都是右值。

递增和递减

递增递减运算符有两种,前置:++i, 后置: i++ , 前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回,一般不用后置版本。
*p++ , 常用的一直方式。
递增递减运算符会改变对象的值,所以在复合表达式中要注意。

whie(*p)
	*p = toupper(*p++);//左右未知结果,因不知先递增还是先返回

c++异常处理

throw 表达式 引发异常/抛出异常
try 语句块 保护代码
catch{} 捕获异常,捕获与throw抛出相对应的异常, catch(…)可以捕获任何异常
示例:

    try
    {
        int a,b;cin >>a >> b;
        if(b == 0)
            throw runtime_error("心态崩了");//抛出异常,初始化runtime_error异常类
        cout << "a/b:" << a/b <<endl;
    }
    catch(runtime_error e1)//捕获异常,runtime_error为类
    {
        cout << e1.what() << endl;
    }

异常类初始化:
exception,bad_alloc, bad_cast对象,不允许为这些对象提供初始值,只能默认初始化;其他类型不同,需要使用string对象或者C风格字符串初始化。
异常类成员函数:
异常类只有一个名为what的成员函数,返回const char* 类型。

异常处理流程:

异常抛出时,首先搜索抛出该异常函数,如果没找到匹配的catch语句,终止该函数,并在调用该函数的函数中继续查找,…, 如果最终未找到,则在标准库函数terminate中查找。
参考这个博客的异常再抛出

定义新的自己的异常

参考菜鸟教程定义新异常

函数

函数声明

函数声明定义与变量类似,在头文件声明,在源文件定义。

参数传递

按值传递 指针形参 引用形参

按值传递,实参的拷贝传递给形参;指针形参虽然也是指针的拷贝值,但指针可以间接访问其所指对象,所以可以改变实参;引用是实参别名,可以改变实参。C++,建议使用引用类型的形参代替指针
引用还有一个好处是可以引用可以避免拷贝,拷贝大的类类型对象或者容器对象比较复杂,或者某些类型(包括IO类型在内)不能拷贝,只能通过引用形参。

main:处理命令行选项

main()函数可以传递参数,

int main(int argc, char *argv[]){...}

第一个形参agrc,表示数组中字符串的数量;第二个参数argv是一个数组,它是指向C风格字符串的指针,第一个元素指向程序的名字或空字符串,最后一个指针之后的元素值保证为0。 即argv实参,从agrv[1]开始.

initializer_list 形参

如果实参数量未知,但类型全部相同,可以使用该关键字,该关键字被定义在同名头文件里。

函数返回值

返回局部对象的引用是错误的,返回局部对象的指针也是错误的。

引用返回左值

调用一个返回引用的函数得到左值,其他返回类型得到右值。

char &get_val(string &str, int ix)//函数返回引用
{
	return str[ix]
}
int main()
{
	string s("abcdef");
	get_val(s,2) = 'A';//将s[2]改为'A'
}

列表初始化返回值

函数可以返回花括号包围的值的列表。

使用尾置返回类型 (未完成)

auto func(int i) -> int(*)[10];//func返回的是一个指针,并且指向了10个整数的数组。

内联函数和constexpr函数

调用函数包含一系列操作,内联函数可避免不必要开销。内联函数使用inline声明
constexpr函数

constexpr size_t scale(size_t cnt){return new_sz() *cnt;}
int arr[scale(2)];//正确,scale(2)是常量表达式
int i = 2;
int a2[scale(i)];//错误

内联函数和constexpr都应该定义在头文件里

  • 使用constexpr,要求返回类型和形参都是字面值类型。
  • constexpr函数不一定返回常量表达式。

成员函数

定义在类内部的成员函数都是隐式inline函数

引入this

在成员函数nebula,可以直接使用调用该函数的对象的成员,这是通过隐式this实现的,即看似 直接使用item,实则是this->item。
this是一个常量指针不允许改变this中保存的地址

引入const成员函数

成员函数有时不需要改变对象本身,此时可以在函数后面加const,有利于提高函数灵活性。

class myClass
{
private:
	void mytest() const// const 成员函数
	{
		... 
	}
}

const成员函数将this从 myClass *const 改为 const myClass * const, 这样有利于提高函数的灵活性。原因在于改之前,常量对象不能调用普通的成员函数

类编译顺序

首先编译函数成员,然后才编译成员函数,所以成员函数可以访问成员,而不论其声明顺序。

构造函数

  • 构造函数不能声明为const
  • 构造函数没有返回类型
  • 只有类没有声明任何构造函数时,编辑器才会自动生成默认构造函数
构造函数初始值列表
class myClass
{
	myClass(int a, string b) : height(a), name(b)
	{
	}
private:
	int height;
	string name;
}

有时候构造函数初始值列表可以通过在构造函数体内用赋值实现,以下情况只能用列表实现:

  • 成员是const
  • 成员是引用
  • 成员属于某种未提供默认构造函数的类类型
委托构造函数

一个构造函数只完成部分成员初始化过程,需要通过其他构造函数来完成。

class myClass
{
	myClass(int a, string b) : height(a), name(b)
	{
	}
	myClass(int a): myClass(a,"test")//委托构造函数
	{
	}
private:
	int height;
	string name;
}
转换构造函数 隐式转换

转换构造函数,类似于默认初始化形式,对参数进行转化,比如:

item.combie(myclass a);//函数声明
string book;
item.combine(book);//转换,string实参创建了一个myclass临时变量,并将它赋给combine

只允许一步转换,比如item.combine(“9999”)是错误的,需要先转换为string再转换为myclass,

explicit抑制隐式转换

类内声明构造函数时用explicit,如果在外部定义,此时定义前面不需要加。

友元

友元作用:使得其他类或函数可以访问该类的成员。
友元分为声明和定义,
声明需要进行两次,一次出现在类的内部,作用是表明友元性质;一次是出现在类外面,使得类的用户可见,从而调用该友元。

class myClass
{
	friend int add(myClass a);//一次声明
}
int add(my Class a);//二次声明
//未定义

通常,应将友元的声明和类本身放置在同一头文件中,即头文件既提供类内的友元声明,也提供类外的外部声明

类的声明

一个类的类型不能是他自己,只能是自身类型的指针或引用

class myClass
{
	myClass *next;
	myClass *prev;
}

名字查找和类的作用域(未完成)

一般流程:在名字所在块寻找其声明语句(名字之前);如果没找到,继续查找外层作用域,最终没找到,程序报错。
类不同于一般流程,类的编译过程:首先处理完类的全部声明才会处理成员函数的定义,因此成员函数可以用到类的所有定义的名字。

类成员声明的名字

typedef int test;

class myClass
{
	test balace() {}
}

上面的test用于类成员声明,只在test balance之前查找,而不在后面查找;因为没有查找到,因此在外层继续查找。

类内不可重新定义外部的变量

一般来说,内层作用域可以重新定义外层作用域的名字,即时名字已经在内层使用过。但是类不允许。

typedef int test;
class myClass
{
	test balace(){}
	typedef int test;
	test val;//error,即使与外部定义相同,也不允许
}
成员函数普通作用域名字查找
  • 首先在成员函数内部,名字之前查找声明,如果没有
  • 在类内继续查找,此时所有成员都可以考虑
  • 类内也没查找到,在类外定义的作用域查找(之前)

类的静态成员

实现方式:在成员的声明之前加上关键字static。

  • 类的静态成员存在于任何对象之外,对象不包含任何与静态数据成员有关的数据(但可以通过对象访问静态成员)
  • 静态成员函数不与任何对象绑定在一起,不包含this指针
    两种方式访问静态成员:
  1. 作用域运算符
class myClass
{
	static void rate(){}
}
double r;
r = myClass::rate();
  1. 类的对象,引用或指针
myclass a1;
a1.rate();
myClass a2;
a2->rate();
  1. 类内其他成员访问静态对象不需要作用域运算符
myClass
{
	void test()
	{
		rate();//不需要作用域运算符
	}
	static void rate();
}
  1. 定义静态成员
  • 静态成员函数定义可以在类内或者类外,必须用static声明在类内,在类外定义时,不能再重复使用static;
  • 静态数据成员不是由构造函数初始化的,必须在类的外部定义和初始化每个静态成员。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值