1. 指针
1.1 指针和函数
在C++中,函数的参数传递方式有三种:值传递、地址传递和引用传递(C++特性)。
- 值传递
- 这种方式使用变量、常量、数组元素作为函数参数,实际是将实参的值复制到形参相应的存储单元中,即形参和实参分别占用不同的存储单元,这种传递方式称为"参数的值传递"或者"函数的传值调用"。
- 值传递的特点是单向传递,即主调函数调用时给形参分配存储单元,把实参的值传递给形参,在调用结束后,形参的存储单元被释放,而形参值的任何变化都不会影响到实参的值,实参的存储单元仍保留并维持数值不变。
#include <iostream>
/* 变量x、y为Swap函数的形式参数 */
void Swap(int x, int y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
std::cout << "&x = " << &x << ", &y = " << &y << std::endl;
std::cout << "x = " << x << ", y = " << y << std::endl;
}
int main(void)
{
int a = 10;
int b = 20;
/*变量a、b为Swap函数的实际参数*/
Swap(a, b);
std::cout << "&a = " << &a << ", &b = " << &b << std::endl;
std::cout << "a = " << a << ", b = " << b << std::endl;
return 0;
}函数在调用时,隐含地把实参a的值赋值给了参数x,而将实参b的值赋值给了参数y,如下面的代码所示:
/*将a的值赋值给x(隐含动作)*/
int x = a;
/*将a的值赋值给y(隐含动作)*/
int y = b;- 地址传递
- 这种方式使用数组名或者指针作为函数参数,传递的是该数组的首地址或指针的值,而形参接收到的是地址。即指向实参的存储单元,形参和实参占用相同的存储单元,这种传递方式称为"参数的地址传递"。
- 地址传递的特点是形参并不存在存储空间,编译系统不为形参数组分配内存。数组名或指针就是一组连续空间的首地址。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该首地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。
#include <iostream>
/* 变量x、y为Swap函数的形式参数 */
void Swap(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
std::cout << "x = " << x << ", y = " << y << std::endl;
std::cout << "*x = " << *x << ", *y = " << *y << std::endl;
}
int main(void)
{
int a = 10;
int b = 20;
/* 变量a、b为Swap函数的实际参数*/
Swap(&a, &b);
std::cout << "&a = " << &a << ", &b = " << &b << std::endl;
std::cout << "a = " << a << ", b = " << b << std::endl;
return 0;
}注意,这里与值传递方式存在着很大的区别。在值传递方式中,传递的是变量a、b的内容(即在上面 的值传递示例代码中,将a、b的内容传递给参数x、y);而这里的地址传递方式则是将变量a、b的 地址值(&a、&b)传递给参数x、y。在上述代码中也可以看到a的地址和x地址完全一样,证明在内存 中表示同一块区域,因此对x指向的内容修改,a存储的内容也被改变
1.1.1 函数形参改变实参的值
即地址传递。在向函数传递参数时,传递参数的地址。
练习:实现一个函数,要求将小写字母变为大写字母
#include<iostream>
void func(char* c)
{
*c -= 32;
}
int main()
{
char c = '0';
do
{
std::cout << "输入一个小写字母:";
std::cin >> c;
while (getchar() != '\n');
} while (c < 'a' || c > 'z');
func(&c);
std::cout << "大写形式是: "<< c << std::endl;
return 0;
}1.1.2 数组名作为函数参数
数组名做函数参数,函数的形参会退化为指针。
当数组名作为函数参数时,实际上传递的是数组的首地址,也就是指向数组第一个元素的指针。因此,函数的形参在接收数组时,会自动退化为指向该数组第一个元素的指针。
这种退化过程是隐式的,编译器会自动进行类型转换。
在printArray函数中,打印结果永远为8,因为这是指针的长度,并不是数组长度
#include<iostream>
void printArray(const int* a)
{
std::cout << "sizeof(a) = "<< sizeof(a) << std::endl; //8
}
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = sizeof(a) / sizeof(a[0]);
std::cout << "sizeof(a) = "<< sizeof(a) << std::endl; //36
//数组名做函数参数
printArray(a);
return 0;
}若想在printArray函数中打印出这个数组,需要传入数组长度
#include<iostream>
void printArray(const int* a, int n)
{
std::cout << "sizeof(a) = "<< sizeof(a) << std::endl; //8
for (int i = 0; i < n; i++)
{
std::cout << a[i] << " ";
}
std::cout << std::endl;
}
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = sizeof(a) / sizeof(a[0]);
std::cout << "sizeof(a) = "<< sizeof(a) << std::endl; //36
//数组名做函数参数
printArray(a, n);
return 0;
}1.1.3 指针函数与函数指针
- 指针函数: 一个返回值指针的函数,其本质是一个函数,而该函数的返回值是指针。(不要返回局部变量的地址,因为局部变量的生命周期在函数结束后就消失了。)
#include <iostream>
int a = 10;
int* getA() //指针函数
{
return &a;
}
int main()
{
int* pa = getA();
*pa = 11;
std::cout << "a = " << a << std::endl;
return 0;
}- 函数指针 函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址
#include <iostream>
int sum(int a, int b)
{
return a + b;
}
int main()
{
std::cout << "sum = " << sum << std::endl;
std::cout << "&sum = " << &sum << std::endl;
return 0;
}&不是必须的,因为函数名就表示了它的地址。
函数有自己的地址,指针变量就是用来存储地址的。因此可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。
函数指针的定义
- 格式:返回值类型(*指针变量名)(形参1,形参2...)= NULL;
- = NULL 可省略
#include <iostream>
int sum(int a, int b)
{
return a + b;
}
int main()
{
int (*p)(int, int) = NULL;//函数指针声明
p = sum;//函数指针初始化
int a = p(3, 5);//调用
std::cout << "a = " << a << std::endl;//8
return 0;
}练习:使用函数指针完成四则运算
#include <iostream>
int Add(int num, int num1)
{
return num + num1;
}
int Sub(int num, int num1)
{
return num - num1;
}
int Mul(int num, int num1)
{
return num * num1;
}
int Div(int num, int num1)
{
return num / num1;
}
int main()
{
int num = 0, num1 = 0, result = 0;
char opr = 0;
int (*p_func)(int, int) = NULL;
std::cout << "请输入考题:";
std::cin >> num >> opr>> num1;
switch (opr)
{
case '+':
p_func = Add;
break;
case '-':
p_func = Sub;
break;
case '*':
p_func = Mul;
break;
case '/':
p_func = Div;
break;
}
result = p_func(num, num1);
std::cout << "结果是" << result << std::endl;
return 0;
}1.2 指针和字符串
1.2.1 使用指针定义字符串的方式
字符串的常规访问方法
#include<iostream>
int main()
{
char arr[] = "abc";
std::cout << arr << std::endl;
std::cout << (void *)arr << std::endl;
for (int i = 0;i < strlen(arr);i++)
{
std::cout << arr[i] << std::endl;
}
return 0;
}字符串的本质字符串是由一系列字符(字符数组)组成的序列,以空字符('\0')作为结束标志。
既然字符串的本质是字符数组(只是以\0结尾),而指针可以代表数组,那么是否可以使用指针来代表字符串呢?
使用指针来定义字符串
○ 格式
const char *指针变量 = 字符串; // const不可省略,因为字符串常量放在常量区,所以必须加 const
const char *定义字符串
#include<iostream>
int main()
{
const char* a2 = "abcdefga"; //常量区,数据只可读,不可修改
return 0;
}- 字符数组方式和字符串指针方式定义字符串的区别
const char *p_str = "Hello, world!"; char str[] = "Hello, world!";
在这个例子中,p_str 是一个指向字符的指针,它指向了一个以空字符 ('\0') 结尾的字符数组(即字符串字面量 "Hello, world!")。而字符串字面量时一个常量,放在常量区,他是只读的。 在这个例子中,str 是一个字符数组,并且以字符 "Hello, world!" 以及结尾的空字符 '\0' 来初始化,他是放在栈区的,可读可写。 由此看来,上述例子中的两个"Hello, world!"含义并不相同。
需要注意的是,字符串字面量(如 "Hello, world!")在C++中是存储在只读数据段的,因此你不能修改通过字符串字面量初始化的 char * 指针所指向的内容。如果你需要修改字符串的内容,你应该使用字符数组而不是指向字符串字面量的指针。
- 可通过指针方式和下标方式来访问字符串内的字符
#include<iostream>
int main()
{
const char*a2 = "abcdefga"; //常量区,数据只可读,不可修改
std::cout << *a2 << std::endl; //a
std::cout << a2[4] << std::endl; //e
//*a2 = 'A'; error
//a2[4] = 'q'; //error
return 0;
}- *不能通过string 类型指针定义字符串
在C++中,string 并不是一个内置的数据类型。C语言标准库提供了处理字符串的函数,但字符串本身通常是以字符数组(char 数组)或字符串指针(char *) 的形式表示的。因此,你不能直接使用 string * 类型指针来定义字符串,因为 string 并不是一个有效的类型,而是一个类类型。
std::string和std::string * ""中存放的是char类型的字符,因此std::string *str = "hello world";是错误的
#include<iostream>
int main()
{
std::string str = "hello world";
//std::string *str = "hello world"; //error
std::string *str_ptr = &str; //
return 0;
}1.2.2 通过指针遍历字符串
在C++中,通过指针遍历字符串是一个常见的操作。字符串在C中是以字符数组的形式存储的,并以空字符('\0')作为结束标志。因此,你可以使用字符指针来遍历字符串中的每一个字符。
以下是如何通过指针遍历字符串的步骤:
- 定义一个字符指针,并将其初始化为指向字符串的第一个字符。
- 使用循环结构(如while或for循环)遍历字符串,直到遇到空字符('\0')。
- 在循环内部,通过指针访问并处理每个字符。
- 在每次迭代中,将指针向前移动一位,以便访问下一个字符。
字符数组类型
#include<iostream>
int main()
{
char a1[] = "abcdefga";
std::cout << *a1 << std::endl; //a
for (char *p_a1 = a1; *p_a1 != '\0'; p_a1++)
{
*p_a1 += 1;
std::cout << *p_a1;
}
std::cout << std::endl;
const char* a2 = "abcdefga";
std::cout << *a2 << std::endl;
for (const char* p_a = a2; *p_a != '\0'; p_a++) {
std::cout << *p_a;
}
std::cout << std::endl;
return 0;
}通常情况下,你不需要直接通过指针来操作std::string对象,因为std::string的设计初衷就是为了简化字符串处理并提供类型安全的接口。直接使用下标访问即可
#include<iostream>
int main()
{
std::string str = "hello world";
for (int i = 0; i < str.length(); i++)
{
std::cout << str[i];
}
std::cout << std::endl;
return 0;
}练习:统计字母a出现的次数
#include<iostream>
int main()
{
int count = 0;
const char* a = "abcdefga";
for (const char* p_a = a; *p_a != '\0'; p_a++)
{
if (*p_a == 'a')
{
count++;
}
}
std::cout << "字母a出现" << count << "次" << std::endl;
return 0;
}1.2.3 字符串的一些常用功能
1.2.3.1 字符串拼接
- 字符数组方式 char *strcat(char *dest, const char *src);
参数说明: dest:指向目标字符串的指针,即要将源字符串附加到的字符串。它必须是一个足够大的字符数组,以便容纳附加的源字符串以及一个空字符('\0'),这个空字符将在新内容之后自动添加。 src:指向源字符串的指针,即要附加到目标字符串的字符串。 strcat 函数会将 src 指向的字符串附加到 dest 指向的字符串的末尾。它会从 dest 字符串的结尾开始查找空字符('\0'),然后在此位置开始复制 src 字符串的内容。strcat 函数返回指向目标字符串 dest 的指针。
char c1[20] = "abcdefg";
const char c2[] = "defg";
strcat(c1, c2);
std::cout << c1 << std::endl;
std::cout << c2 << std::endl;需要注意的是,使用 strcat 时必须确保目标字符串 dest 有足够的空间来容纳源字符串 src 的内容,否则会发生缓冲区溢出,这是一个常见的安全漏洞。为了避免这种情况,可以使用 strncat 函数,它允许你指定一个最大字符数来限制复制的字符数量。
- string方式 o 直接+或+=
#include<iostream>
int main()
{
std::string s1 = "abc";
std::string s2 = "def";
std::string s3 = s1 + s2;
std::cout << s3 << std::endl;//abcdef
s1 += s2;
std::cout << s1 << std::endl;//abcdef
return 0;
}1.2.3.2 字符串比较
- 字符数组方式
int strcmp(const char *s1, const char *s2);
该函数接受两个参数,s1 和 s2,它们都是指向要比较的字符串的指针。strcmp 函数按照 ASCII 表的顺序逐个比较两个字符串中的字符,直到出现不同的字符或者遇到字符串结束符 \0。
返回值说明:
如果 s1 和 s2 相同,返回 0。
如果 s1 字典顺序上小于 s2,返回一个负数。
如果 s1 字典顺序上大于 s2,返回一个正数。
这个函数对于区分大小写是敏感的,即它会将大写和小写字母视为不同的字符。
const char c1[20] = "abcdefg";
const char c2[] = "defg";
std::cout << strcmp(c1, c2) << std::endl;- string方式
< == >= <=(比较结果时bool值,以ASCII为准进行比较)
#include<iostream>
int main()
{
std::string s1 = "abc";
std::string s2 = "def";
bool a = (s1 <= s2);
std::cout << (s1 > s2) << std::endl;//0
std::cout << a << std::endl;//1
return 0;
}1.2.3.3 字符串复制
- 字符数组方式 char *strcpy(char *dest, const char *src);
参数说明: dest:指向目标字符串的指针,即要复制到的字符串。它必须是一个足够大的字符数组,以便容纳源字符串的内容,包括末尾的空字符('\0')。 src:指向源字符串的指针,即要复制的字符串。 strcpy 函数从 src 指向的字符串开始复制字符,直到遇到源字符串的结果标志('\0')。然后,在目标字符串的末尾添加一个空字符,以标记字符串的结束。 strcpy 函数返回 dest 字符串的指针。
char c1[20] = "abcdefg";
const char c2[] = "defg";
strcpy(c1, c2);
std::cout << c1 << std::endl;
std::cout << c2 << std::endl;需要注意的是,strcpy 函数不会检查目标字符串 dest 的大小是否足够容纳源字符串 src 的内容。如果目标字符串数组太小,就会发生缓冲区溢出,这是一个严重的安全问题。为了避免这种情况,建议使用 strncpy 函数,它允许你指定一个最大字符数来限制复制的字符数量。
- string方式
#include<iostream>
int main()
{
std::string s1 = "abc";
std::string s2 = "defgwreq";
s1 = s2;
std::cout << s1 << std::endl;//defgwreq
return 0;
}1.2.3.4 字符串查找
- 字符数组方式 char *strstr(const char *haystack, const char *needle);
参数说明: haystack:主串,即要在其中进行搜索的字符串。 needle:子串,即要在主串中查找的字符串。
strstr 函数会返回指向 haystack 中第一次出现 needle 的位置的指针。如果 needle 没有在 haystack 中找到,则返回 NULL。返回的指针指向 haystack 中 needle 的首次出现位置,包括该位置的字符。
char c1[20] = "abcdefgdeadeb";
const char c2[] = "de";
std::cout << strstr(c1, c2) << std::endl; // defgdeadeb
std::cout << c1 << std::endl; // abcdefgdeadeb
std::cout << c2 << std::endl; // de- string方式 字符串1.find(字符串2)
○ 在字符串str1中找到str2第一次出现的位置的下标 ○ 如果没有找到,那么会返回一个特别的标记npos,一般写作 string::npos。
#include<iostream>
int main()
{
std::string s1 = "abcdefgabcdefg";
std::string s2 = "qwe";
int index = s1.find(s2);
if (index != std::string::npos)
{
std::cout << index << std::endl;
}
else
{
std::cout << "未找到" << std::endl;
}
return 0;
}1.2.4 main函数分析
main函数原型:
int main(int argc, char* argv[])
main的含义:
○ main是函数的名称,和我们自定义的函数名称一样,也是一个标识符 ○ 只不过main这个名称比较特殊,程序已启动就会自动调用它
return 0;的含义:
○ 告诉系统main函数是否正确的被执行了 ○ 如果main函数的执行正常,那么就返回0 ○ 如果main函数执行不正常,那么就返回一个非0的数
返回值类型:
○ 一个函数return后面写的是什么类型,函数的返回值类型就必须是什么类型,所以写int
形参列表的含义 ○ int argc; ● 系统在启动程序时调用main函数时传递给argv值的个数 ○ char * argv[]; ● 系统在启动程序时传入的的值,默认情况下系统只会传入一个值,这个值就是main函数执行文件的路径
#include<iostream>
int main(int argc, char* argv[]) {
std::cout << argc << std::endl;
for (int i = 0;i < argc;i++)
{
std::cout << argv[i] << std::endl;
}
return 0;
}也可以通过命令行或项目设置传入其它参数
