c++学习笔记:记在类前

这篇博客详细介绍了C++的基础知识,包括命令行编译、注释规范、变量和基本类型的选用、初始化方法、const限定符的使用、迭代器操作、数组、指针、字符串、向量、条件运算符、switch语句、异常处理以及函数的特性和重载。此外,还讨论了局部对象、函数指针和默认参数等特殊语言特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 开始
    从命令行运行编译器
$ CC prog1.cc

注释:以 /* 和 */ 开始结束的注释不能嵌套,尽量使用单行注释,注释快捷键ctrl+shift+/

  1. 变量和基本类型
bool
char8位
wchar_t16位
char16_t16位
char32_t32位
short16位
int16位
long32位
long long64位
float6位有效数字
double10位有效数字
long double10位有效数字

当明确知晓数值不可能为负时,选用无符号类型;
执行浮点数运算选用double;
负数转换为无符号数时等于这个负数加上无符号数的模。

20十进制
024八进制
0x14十六进制
转义序列
换行符\n
纵向制表符\v
反斜线\
回车符\r
横向制表符\t
退格符\b
问号?
进纸符\f
报警符\a
双引号\"
单引号\’
  • 初始化
int a = 0;
int a = {0};
int a{0};
int a(0);
  • const限定符

const对象必须初始化
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字

extern const int bufSize = fcn();
  • const的引用
int i = 42;//允许将const int&绑定到普通int对象上,但反过来则不行
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
int &r4 = r1 * 2;//错误
  • const的指针
int errNumb = 0;
int *const curErr = &errNumb;//curErr将一直指向errNumb,即该指针是常量,但指针指向的对象并不是常量,可以改变
const double pi =3.14159const double *const pip = π//pip是一个指向常量对象的常量指针

顶层const:指针本身是个常量
底层const:指针指向的对象是个常量

int i = 0int &const p1 = &i;//不能改变p1的值,这是一个顶层const
const int ci = 42;//不能改变ci的值,这是一个顶层const
const int *p2 = &ci;//允许改变p2的值,这是一个底层const
const int *const p3 = p2;//靠右的是顶层const,靠左的是底层const
const int &r = ci;//用于声明引用的const都是底层const

实际原则应该就是非常量可以等于常量,反之则不成

  • constexpr
//constexpr指值不会改变且在编译过程就得到计算结果的表达式
constexpr int mf = 20//必须用常量或常量表达式初始化
constexpr int limit = mf + 1constexpr int sz = size();//只有当size()是一个constexpr函数时才正确

与const有所不同,constexpr只对指针生效,与指针所指对象无关

  • typedef和using
typedef double wages;//wages是double的同义词
typedef wages base, *p;//base是double的同义词,p是double*的同义词
using SI = Sales_item;//SI是Sales_item的同义词
//一个常量指针类型别名的易错例子
typedef char *pstring;
const pstring cstr = 0;//cstr是指向char的常量指针
const pstring *ps;//ps是一个指针,它的对象是指向char的常量指针
//const是对给定类型的修饰,pstring实际上是指向char的指针,因此,const pstring就是指向char的常量指针,而非指向常量字符的指针
const char *cstr = 0;//const pstring cstr的一种错误理解
//声明语句中用到pstring时,其基本数据类型是指针
//用char*重写该语句后,数据类型就变成了char,*成为了声明符的一部分
//这样改写的结果就是const char成了基本数据类型
//前者声明了一个指向char的常量指针,后者声明了一个指向const char的指针
  • auto

auto必须有初始值,并且该语句中所有变量的基本数据类型必须一样

//auto一般会忽略掉顶层const
int i= 0,&r = i;
auto a = r;
const int ci = i,&cr = ci;
auto b = ci;//b是一个整型(ci的顶层const被忽略掉了)
auto c = cr;//c是一个整型(cr是ci的别名,ci本身是一个顶层const)
auto d = &i;//d是一个整型指针
auto e = &ci;//e是一个指向整型常量的指针
  • decltype
    选择并返回操作数的数据类型
const int ci = 0,&cj = ci;
decltype(ci) x = 0;//x的类型是const int
decltype(cj) y = x;//y的类型是const int&,y绑定到变量x
decltype(cj) z;//错误,引用必须初始化
//decltype的表达式如果是加上了括号的变量,结果将是引用
decltype((i)) d;//错误:d是int&,必须初始化
decltype(i) e;//正确:e是一个(未初始化的)int
//decltype后如果跟的是双括号,结果永远都是引用
  1. 字符串、向量和数组
  • string
    初始化
string s1;
string s2 = s1;
string s3 = "hiya";
string s4(10,'c');
string s5("value");
string s6(s1);
string操作
os<<s将s写到输出流当中,返回os
is>>s从is中读取字符串赋给s,字符串以空白分割,返回is
getline(is,s)从s中读取一行赋给is
s.empty()s为空返回true,否则返回false
s.size()返回s中字符的个数

两个对象操作时若想操作结果还是string,则必须确保每个加法运算符(+)的两侧的运算对象至少有一个string;
※诸如“hello”这种不是string,是char

//小tip:一个处理string中每个字符的小代码段
string s("Hello World!");
//转换成大写形势
for(auto &c : s)//注意:c是引用
	c = toupper(c);//c是一个引用,因此赋值语句将改变s中字符的值
cout<< s <<endl;
//只有用引用才能改变s中的值,否则改变的只是临时变量
  • vector
    初始化
vector<T> v1
vector<T> v2(v1)
vector<T> v2 = v1
vector<T> v3(n,val)
vector<T> v4(n)
vector<T> v5{a,b,c...}
vector<T> v5 = {a,b,c...}

※push_back

vector<int> v2;
for(int i = 0;i != 100;i++)
	v2.push_back(i);//依次把整型值放在v2尾端

vector只能对确知已存在的元素执行下标操作

//错误示例
vector<int> ivec;
for(decltype(ivec.size()) ix = 0;ix != 10; ++ix)
	ivec[ix] = ix;//错误:ivec不包含任何元素

迭代器
begin负责返回指向第一个元素的迭代器,end负责返回指向容器尾元素的下一位置的迭代器

迭代器运算符
*iter返回iter所指元素的引用
iter->mem解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
++iter令iter指向容器中的下一个元素
–iter令iter指向容器中的上一个元素
iter1 == iter2判断两个迭代器指示的元素是否相等
iter1 != iter2

迭代器类型
iterator:读写
const_iterator:只读
迭代器返回的类型由元素本身的类型决定,只有使用cbegin和cend时返回的才永远都是常量,即const_iterator

//解引用操作
(*it).empty();//正确,解引用it,调用对象的empty成员
*it.empty;//错误,试图访问it的名为empty的成员,但it是个迭代器,没有empty成员

箭头运算符:(->)
it->mem和(*it).mem表达的意思相同
目前已知的vector对象的操作对迭代器的限制:不能在范围for循环中向vector添加元素;任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效

迭代器二分搜索:

int main()
{
 vector<string> text{ "1","2","3","4","5","6","7","8","9","0" };
 auto beg = text.begin(), end = text.end();
 auto mid = text.begin() + (end - beg) / 2;
 while (mid != end && *mid != "1")
 {
  if ("1" < *mid)
   end = mid;
  else
   beg = mid + 1;
  mid = beg + (end - beg) / 2;
  cout << *mid << endl;
 }
 return 0;
}
  • 数组
    初始化
//纬度必须是常量
unsigned cnt = 42;//不是常量表达式
constexpr unsigned sz = 42;//常量表达式
int arr[10];//含有10个整数的数组
int *parr[sz];//含有42个整型指针的数组
string bad[cnt];//错误:cnt不是常量表达式
string strs[get_size()];//只有当get_size是constexpr时正确

不存在引用的数组;
不允许拷贝和赋值。

指针和数组

string num[] = {"one","two","three"};
string *p = &nums[0];//指向nums的第一个元素
string *p2 = num;//和上面一行等价

int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(ia);//ia2是一个整型指针,指向ia的第一个元素
auto ia2(&ia[0]);//和上面一行等价

//标准库函数begin和end
int *beg = begin(ia);//指向ia首元素的指针
int *last = end(ia);//指向ia尾元素的下一位置的指针

数组内置的下标运算符索引值不是无符号类型,这一点与vector和string不一样

c风格字符串

c风格字符串函数
strlen()返回长度,空字符不计算在内
strcmp(p1,p2)若p1大于p2,返回一个正值,相反同理
strcat(p1,p2)把p2附加到p1,返回p1
strcpy(p1,p2)把p2拷贝给p2,返回p1

传入此类函数的指针必须指向以空字符作为结束的数组

//以空字符结束的字符数组可以初始化string或为string赋值
char *str = s;//错误:不能用string对象初始化char
const char *str = s.ctr();//正确
//使用数组初始化vector对象
int int_arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));

多维数组

//使用引用对多维数组进行循环操作,避免数组被自动转成指针
//一个错误例子
for(auto row : ia)
 for(auto col : row)
 	cout<< col <<endl;//无法通过编译,因为第一个row被转为指针int*,如此第二个循环就不合法了。
for(auto &row : ia)
 for(auto &col : row)
 	cout<< col <<endl;//正确写法
//多维数组指针操作
int ia[3][4];
for(auto p = ia;p != ia + 3; ++p)
{
	for(auto q = *p;q != *p + 4; ++q)
		cout<< *q <<endl;
}
  1. 表达式
  • 取余和除法运算
左右对应
(-m/n)和m/(-n)-(m/n)
m%(-n)m%n
(-m)%n-(m%n)
  • 成员访问运算符
    ptr->mem等价于*(ptr).mem
    解引用运算符的优先级低于点运算符
*p.size();//是错误的,p是一个指针,它没有名为size的成员
(*p).size();//正确
  • 条件运算符
    cond?expr1:expr2 嵌套时从左至右判断
  • sizeof运算符
    在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用,sizeof不需要真的解引用也能知道它所指对象的类型
  • ※类型转换
bool flag;
char cval;
short sval;
int ival;
long lval;
float fval;
unsigned short usval;
unsigned int uival;
unsigned long ulval;
double dval;
//一些例子
3.14159L+'a';//'a'提升为int,然后该int值转换为long double
dval+ival;//ival转换为double
dval+fval;//fval转换为double
ival=dval;//dval切除小数部分后转换为int
flag=dval;//如果dval是0,则flag是false,否则是true
cval+fval;//cval提升为int,然后该int转换为float
sval+cval;//sval和cval都提升为int
cval+lval;//cval转换为long
ival+ulval;//ival转换为unsigned long
usval+ival;//根据unsigned short和int所占空间的大小进行提升
uival+lval;//根据unsigned int和long所占空间的大小进行转换

显式转换
static_cast<new_type>expression
一种强制类型转换;
只要不包括底层const就都可以使用;
不能将const类型转换为非const类型;
可以将void指针转换为初始指针类型,也可以将初始指针类型转换为void类型的指针

cosnt_cast<new_type>expression
const_cast只能改变运算对象的底层const

const char *pc;
char *p = const_cast<char*>(pc);//正确:但是通过p写值是未定义的行为

只有const_cast能改变表达式的常量属性;
不能用const_cast改变表达式的类型;

  1. 语句
  • switch
    如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显式地中断了这一过程,否则直到switch的结尾处才会停下来。要想避免执行后续case分支的代码,我们必须显式地告诉编译器终止执行过程。大多数情况下,在下一个case标签之前应该有一条break语句。
unsigned vowelcnt = 0;
//...
switch(ch)
{
	//出现aeiou任意一个都会将vowelcnt加1
	case 'a';
	case 'e';
	case 'i';
	case 'o';
	case 'u';
		++vowelcnt;
		break;
}

如果没有任何一个case标签能匹配上switch表达式的值,程序将执行紧跟在default标签后面的语句
break:可以存在于while、do while、for、switch
continue:可以存在于for、while、do while

  • 异常处理
    throw:throw用来(raise)引发异常
    try:以一个或多个catch子句结束。try抛出的异常被catch子句处理
    try{
    program-statements
    }catch(exception-declaration){
    handler-statements
    }catch(exception-declaration){
    handler-statements
    }//…
//异常处理
//若除数为0,抛出异常
double division(int a, int b)
{
    if (b == 0)
    {
        throw "Division by zero condition!";
    }
    return (a / b);
}
int main()
{
    int x = 50;
    int y = 0;
    double z = 0;
    try {
        z = division(x, y);
        cout << z << endl;
    }
    catch (const char* msg) {
        cerr << msg << endl;
    }
    return 0;
}
  1. 函数
  • 局部对象
    局部静态对象:在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响
size_t count_calls()
{
	static size_t ctr = 0;//调用结束后这个值仍然有效
	return ++ctr;
}
int main()
{
	for(size_t i = 0;i != 10;++i)
		cout<< count_calls() <<endl;
	return 0;
}

不能返回局部对象的引用或指针

//试图返回局部对象的引用
const string &mainp()
{
	string ret;
	//以某种方式改变一下ret
	if(!ret.empty())
		return ret;//错误:返回局部对象的引用
	else
		return "Empty";//错误:"Empty"是一个局部临时量
}
  • 传引用形参

通过使用引用形参,允许函数改变一个或多个实参的值

//返回s中c第一次出现的位置索引
//引用形参occurs负责统计c出现的次数
string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
	auto ret = s.size();
	occurs = 0;
	for(decltype(ret) i = 0;i != s.size();++i)
	{
		if(s[i] == c)
		{
			if(ret == s.size())
				ret = i;
			++occurs;
		}
	}
	return 0;
}
  • 重载函数

编译器根据传递的实参类型判断使用哪个函数;
不允许两个函数除了返回类型外其他要素都相同

※重载和const形参

Record lookup(Account&);
Record lookup(const Account&);
Record lookup(Account*);
Record lookup(const Account*);
//上面的重载函数都是正确的,编译器可以通过实参是否是常量来推断调用哪个函数
//因为const不能转换为非const,因此只能把const实参传给const形参
//但非const可以转换为const,因此非常量形参可以调用以上任意函数
  • 特殊用途语言特性

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值

  • 函数指针
//函数指针指向的是函数而非对象
bool (*pf)(const string &,const string &);
//pf两端的括号必不可少,如果不写括号,则pf是一个返回值为bool指针的函数

返回指向函数的指针

using F = int(int*,int);//F是函数类型,不是指针
using PF = int(*)(int*,int);//PF是指针类型
PF f1(int);//正确:PF是指向函数的指针,f1返回指向函数的指针
F f1(int);//错误:F是函数类型,f1不能返回一个函数,返回了一个int
F *f1(int);//正确:显式地指定返回类型是指向函数的指针
int (*f1(int)(int*,int);//直接声明
auto f1(int) -> int(*)(int*,int);//尾置返回类型声明

至此,类前的复习完毕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值