const 指针 不可以改变的 不能修改的指向的对象
const 可以放在int*前也可以放后面
*指针 const 常量 可以一起读出来
区分 普通变量 和指针变量
普通变量和指针变量是编程中的两种基本类型,它们在内存中的表现和用途有所不同。下面是它们的区分和详细解释:
### 普通变量
1. **定义和用途**:
- 普通变量直接存储数据值。
- 它可以是基本数据类型(如整型、浮点型、字符型)或复合数据类型(如结构体)。
2. **存储方式**:
- 普通变量在内存中占据一个特定的地址,该地址存放的是变量的实际值。
3. **使用举例**:
```c
int a = 10; // 变量 a 直接存储数值 10
```
4. **访问方式**:
- 通过变量名直接访问其存储的值。
### 指针变量
1. **定义和用途**:
- 指针变量存储的是另一个变量的地址。
- 通过指针可以间接访问或修改该地址对应的变量的值。
2. **存储方式**:
- 指针变量本身也占据一个特定的内存地址,但它存放的是一个指向其它变量的内存地址。
3. **使用举例**:
```c
int a = 10; // 普通变量 a
int *p = &a; // 指针变量 p 存储了 a 的地址
```
4. **访问方式**:
- 通过解引用操作符(`*`),可以访问指针变量所指向的地址处存储的值。
- 通过取地址操作符(`&`),可以获取一个普通变量的地址并赋值给指针变量。
### 例子对比
下面是一个详细的例子,展示了普通变量和指针变量的使用和区别:
```c
#include <stdio.h>
int main() {
int a = 10; // 定义普通变量 a
int *p = &a; // 定义指针变量 p,并使其指向 a 的地址
// 输出普通变量 a 的值
printf("Value of a: %d\n", a);
// 输出指针变量 p 所存储的地址
printf("Address stored in pointer p: %p\n", p);
// 通过指针 p 访问 a 的值
printf("Value pointed to by p: %d\n", *p);
// 修改 a 的值
a = 20;
printf("Value of a after modification: %d\n", a);
printf("Value pointed to by p after modification: %d\n", *p);
// 通过指针 p 修改 a 的值
*p = 30;
printf("Value of a after modification through pointer p: %d\n", a);
printf("Value pointed to by p after modification through pointer p: %d\n", *p);
return 0;
}
```
### 解释
1. **普通变量 `a`**:
- 直接存储数值 10。
- 修改 `a` 的值直接反映在输出中。
2. **指针变量 `p`**:
- 存储 `a` 的地址。
- 通过解引用操作符(`*p`),可以访问或修改 `a` 的值。
通过上述例子和解释,可以清楚地看到普通变量和指针变量在定义、存储、访问和使用上的不同。普通变量直接存储数据,而指针变量存储的是数据的地址,通过指针可以间接访问或修改数据。
多级指针 指向指针的指针
操作地址 操作内存 的角度?
函数指针 &指针函数
在C++中,`virtual public` 是用于声明虚继承的一个关键字组合。当一个类通过多条路径继承同一个基类时,虚继承可以防止基类的多重副本在派生类中出现,从而解决所谓的“钻石继承问题”。
### 钻石继承问题
让我们先看看什么是钻石继承问题:
```cpp
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "I can eat!" << endl;
}
};
class Mammal : public Animal {
};
class Bird : public Animal {
};
class BirdDog : public Mammal, public Bird {
};
int main() {
BirdDog myBirdDog;
// myBirdDog.eat(); // 这行代码会导致编译错误
return 0;
}
```
在上述代码中,`BirdDog` 类通过 `Mammal` 和 `Bird` 类继承了 `Animal` 类,这导致 `BirdDog` 类有两份 `Animal` 类的副本。尝试调用 `myBirdDog.eat()` 会引起编译错误,因为编译器不知道该调用哪一个 `Animal` 类的 `eat` 方法。
### 虚继承的解决方案
使用虚继承来解决这个问题:
```cpp
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "I can eat!" << endl;
}
};
class Mammal : virtual public Animal {
};
class Bird : virtual public Animal {
};
class BirdDog : public Mammal, public Bird {
};
int main() {
BirdDog myBirdDog;
myBirdDog.eat(); // 正常工作:I can eat!
return 0;
}
```
### 详细解释
1. **基类 `Animal`**:
```cpp
class Animal {
public:
void eat() {
cout << "I can eat!" << endl;
}
};
```
- `Animal` 类有一个方法 `eat`。
2. **中间类 `Mammal` 和 `Bird`**:
```cpp
class Mammal : virtual public Animal {
};
class Bird : virtual public Animal {
};
```
- `Mammal` 和 `Bird` 类都使用 `virtual public` 关键字虚继承 `Animal` 类。
- 这确保了当 `BirdDog` 类继承自 `Mammal` 和 `Bird` 时,它只会有一份 `Animal` 类的副本。
3. **派生类 `BirdDog`**:
```cpp
class BirdDog : public Mammal, public Bird {
};
```
- `BirdDog` 类继承自 `Mammal` 和 `Bird`。
4. **在 `main` 函数中使用 `BirdDog` 对象**:
```cpp
int main() {
BirdDog myBirdDog;
myBirdDog.eat(); // 正常工作:I can eat!
return 0;
}
```
- 由于 `Mammal` 和 `Bird` 虚继承了 `Animal`,`BirdDog` 类中只有一份 `Animal` 类的副本,所以调用 `myBirdDog.eat()` 能正常工作。
### 虚继承的内部机制
虚继承通过引入一个虚基类表(Virtual Base Table, VBT)来解决多重继承中的二义性问题。每个虚继承的派生类会包含一个指向 VBT 的指针,通过这个指针,派生类可以正确地访问虚基类的成员。
虚继承使得编译器在构造和析构派生类对象时能够确保虚基类只被初始化和清理一次。这样就有效地避免了多重继承中的副本问题,同时也提供了更好的内存管理和更清晰的继承结构。
通过虚继承,我们可以创建更复杂的类层次结构,而不必担心基类的多重副本问题。这使得代码更具可维护性和可扩展性。
多态(Polymorphism)是面向对象编程中一个重要的概念,它允许不同类的对象对同一消息(方法调用)做出不同的响应。多态性提高了代码的灵活性和可扩展性,是面向对象设计的核心特性之一。在C++中,多态性可以通过虚函数(virtual functions)和函数重载(function overloading)来实现。
### 多态的基本概念
1. **静态多态性(编译时多态性)**:
- 函数重载(Function Overloading):同一作用域内,函数名相同但参数列表不同的函数,可以进行重载。
- 这种多态性在编译时即可确定,编译器根据调用函数时的参数类型选择合适的函数。
2. **动态多态性(运行时多态性)**:
- 通过虚函数实现的动态多态性。在运行时,根据对象的实际类型来调用相应的函数,实现方法的覆盖和动态绑定。
- 这种多态性使得程序在运行时能够适应不同的对象,执行对应的操作。
### 虚函数与动态绑定
在C++中,通过在基类中声明虚函数(使用 `virtual` 关键字),可以实现动态多态性。派生类可以覆盖(重写)基类的虚函数,并根据实际对象类型来调用对应的函数。
#### 示例代码:
```cpp
#include <iostream>
using namespace std;
// 基类 Shape
class Shape {
public:
virtual void draw() {
cout << "Drawing a shape." << endl;
}
};
// 派生类 Rectangle
class Rectangle : public Shape {
public:
void draw() override {
cout << "Drawing a rectangle." << endl;
}
};
// 派生类 Circle
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a circle." << endl;
}
};
int main() {
Shape* shape1 = new Rectangle();
Shape* shape2 = new Circle();
shape1->draw(); // 动态绑定到 Rectangle 类的 draw 方法
shape2->draw(); // 动态绑定到 Circle 类的 draw 方法
delete shape1;
delete shape2;
return 0;
}
```
#### 详细解释:
1. **基类 `Shape`**:
- 定义了一个虚函数 `draw()`,该函数用于绘制形状。
2. **派生类 `Rectangle` 和 `Circle`**:
- 分别重写了基类 `Shape` 的 `draw()` 函数,实现了各自特定形状的绘制。
3. **`main` 函数中的示例**:
- 创建了两个指向基类 `Shape` 的指针 `shape1` 和 `shape2`。
- 分别让它们指向派生类 `Rectangle` 和 `Circle` 的对象。
- 调用 `shape1->draw()` 和 `shape2->draw()` 时,会根据指针所指向的对象类型来动态决定调用哪个版本的 `draw()` 函数。
4. **动态绑定**:
- 在运行时,程序会根据指针所指向的实际对象类型(而不是指针类型)来决定调用哪个函数。这就是动态绑定或运行时多态性的体现。
### 多态的难点与注意事项
1. **理解虚函数的机制**:
- 需要理解虚函数表(vtable)和虚函数指针(vptr)在实现多态时的作用。
- 虚函数表是每个包含虚函数的类的静态数据成员,存储了指向虚函数地址的指针。
2. **虚函数的正确使用**:
- 必须在基类中使用 `virtual` 关键字声明虚函数,并在派生类中进行覆盖。
- 不要在构造函数、析构函数和友元函数中使用虚函数,因为这些函数的调用不会进行动态绑定。
3. **静态绑定与动态绑定的区别**:
- 静态绑定发生在编译时,即编译器根据指针或引用的静态类型来决定调用哪个函数。
- 动态绑定发生在运行时,根据对象的实际类型来决定调用哪个函数。
4. **多态的优点**:
- 提高了代码的灵活性和可扩展性,允许添加新的派生类而无需修改现有代码。
- 支持以统一的方式处理不同类型的对象,增强了代码的复用性和可维护性。
5. **多态的实现原理**:
- C++通过虚函数表和虚函数指针来实现多态性,这需要一些额外的内存开销和运行时开销。
6. **虚函数的性能影响**:
- 虚函数调用比普通函数调用稍微慢一些,因为它需要通过虚函数表进行间接调用。
### 总结
多态是面向对象编程的重要特性,通过虚函数和函数重载实现了编译时和运行时的多态性。理解多态的内部机制以及正确地使用虚函数可以帮助设计出更加灵活和可扩展的类结构,从而提高代码的可维护性和复用性。多态性是C++等面向对象语言中的一个关键概念,对于初学者来说可能需要一定时间和练习才能完全掌握其概念和应用。
地址空间随机化 ASLR 加密保护 防止反向编译
反向汇编 很常见的 c 最终都是一些库 函数的定义 和 参数是可以拿到的
java反汇编 更容易 好想学啊
指针和数组
二维数组 指针数组的表达
一维数组 本身指针指向另外的数组?
听着隔壁班老师说的 感觉他们做的好有意思呀
哈哈 安卓看样子是很好玩的嘛
但是之前就做过 不知道现在发展的怎么样了
字符串常量 和 c字符串数组的区别
在C语言中,字符串常量(String Literal)和C字符串数组(C String Array)虽然都可以表示字符串,但它们在内部存储和使用方式上有一些区别。
### 字符串常量(String Literal)
字符串常量是一种特殊的常量,在C语言中用双引号括起来的字符序列。例如:
```c
char *str1 = "Hello";
char str2[] = "World";
```
- **存储位置**:字符串常量存储在静态存储区域(常量区),通常是在可执行文件的只读数据段(`.rodata`)中。
- **不可修改**:字符串常量是不可修改的。尝试修改字符串常量的行为是未定义的,可能导致程序崩溃或不可预料的行为。
- **静态分配**:定义时分配固定大小的存储空间,且大小由编译器确定。
### C字符串数组(C String Array)
C字符串数组是由字符组成的数组,用于存储和操作字符串。
```c
char str3[] = {'H', 'e', 'l', 'l', 'o', '\0'};
char str4[10] = {'W', 'o', 'r', 'l', 'd', '\0'};
```
- **存储位置**:C字符串数组可以存储在不同的存储区域,如堆、栈或全局静态存储区域。
- **可修改**:C字符串数组的内容是可修改的,因为它们存储在可读写的内存区域中。
- **动态分配**:可以根据需要动态分配内存空间,大小可以在运行时确定。
### 主要区别总结
1. **存储位置**:
- 字符串常量存储在只读的静态存储区域。
- C字符串数组可以存储在堆、栈或全局静态存储区域,取决于其定义方式和存储位置。
2. **可修改性**:
- 字符串常量是不可修改的,修改它们的行为是未定义的。
- C字符串数组的内容可以修改,因为它们通常存储在可读写的内存区域中。
3. **定义和分配**:
- 字符串常量在定义时分配固定大小的存储空间,大小由编译器决定。
- C字符串数组可以在编译时或运行时确定大小,可以动态分配内存空间。
### 使用场景
- **字符串常量**通常用于不需要修改字符串内容的情况,如常量初始化、字符串比较等。
- **C字符串数组**适合需要动态操作或修改字符串内容的情况,如字符串拼接、修改等操作。
理解这两者的区别有助于正确地使用和处理字符串在C语言中的表示方式。
C字符串 空字符影藏在最后
拿关系运算符去做
==用于判断空 c没有null只有0 所以0就可以
c++ 有null
指针的关系运算
复杂指针定义的判断
1.先找到起始位置
函数指针*signal
延后执行 保存为函数指针 回调
动态分配内存
对野指针的操作
智能指针
引用的概念
函数参数传递
*和&什么时候用
在C和C++编程中,`*` 和 `&` 是两个非常重要的运算符,分别用于指针和地址的操作。下面是它们的详细用法和区别:
### `*` 运算符(解引用操作符)
`*` 运算符有两个主要用途:
1. **声明指针变量**:
- 在声明一个指针变量时使用。
```cpp
int *ptr; // 声明一个指向int类型变量的指针
```
2. **解引用指针**:
- 获取指针所指向的变量的值。
```cpp
int a = 10;
int *ptr = &a; // ptr指向变量a
int value = *ptr; // value = 10,通过解引用ptr获取a的值
*ptr = 20; // 修改ptr指向的变量a的值
```
### `&` 运算符(取地址操作符)
`&` 运算符有两个主要用途:
1. **获取变量的地址**:
- 返回变量的内存地址。
```cpp
int a = 10;
int *ptr = &a; // 获取a的地址并赋值给指针ptr
```
2. **引用(C++ 特性)**:
- 在C++中,`&` 还可以用来声明引用变量。引用是已存在变量的别名。
```cpp
int a = 10;
int &ref = a; // 声明一个引用ref,它是变量a的别名
ref = 20; // 修改ref也会修改a
```
### 示例代码
下面是一个示例代码,演示了 `*` 和 `&` 的用法:
```cpp
#include <iostream>
int main() {
// 1. 使用&获取变量的地址
int a = 10;
int *ptr = &a; // 获取变量a的地址
std::cout << "Address of a: " << &a << std::endl;
std::cout << "Value of ptr: " << ptr << std::endl;
// 2. 使用*解引用指针
std::cout << "Value of a via ptr: " << *ptr << std::endl;
// 3. 使用引用
int &ref = a; // 声明引用ref,它是变量a的别名
ref = 20; // 修改ref的值会直接修改a
std::cout << "New value of a: " << a << std::endl;
// 4. 修改指针指向的值
*ptr = 30; // 通过指针修改a的值
std::cout << "Updated value of a: " << a << std::endl;
return 0;
}
```
### 输出结果
```
Address of a: 0x7ffd1b07a77c
Value of ptr: 0x7ffd1b07a77c
Value of a via ptr: 10
New value of a: 20
Updated value of a: 30
```
### 总结
- **`*` 运算符**:
- 声明指针变量时使用:`int *ptr;`
- 解引用指针,访问或修改指针指向的值:`*ptr`
- **`&` 运算符**:
- 获取变量的地址:`int *ptr = &a;`
- 声明引用变量(仅C++):`int &ref = a;`
理解和正确使用 `*` 和 `&` 运算符是掌握指针和引用的关键,也是C和C++编程中的基本技能。
const 会影响引用
r+
rewind倒带的意思
定位内部位置指针
attach 结合在一起
#include <stdio.h>int main() {// 文件名const char* filename = "text.txt";// 写入名字到文件FILE* file = fopen(filename, "w"); // 以写模式打开文件if (file == NULL) {perror("Error opening file for writing");return 1;}const char* name = "Your Name"; // 将这里替换为你的名字fprintf(file, "%s", name); // 将名字写入文件fclose(file); // 关闭文件// 读取文件内容file = fopen(filename, "r"); // 以读模式打开文件if (file == NULL) {perror("Error opening file for reading");return 1;}char buffer[256]; // 缓冲区用于存储读取的文件内容if (fgets(buffer, sizeof(buffer), file) != NULL) { // 读取文件内容printf("Content of file: %s\n", buffer); // 打印文件内容} else {perror("Error reading file");}fclose(file); // 关闭文件return 0;
}
关于一定要用const的回答
在C语言和C++中使用`const`关键字不是必须的,但它是一种良好的编程实践,有助于代码的可读性、安全性和维护性。以下是一些关于何时和为什么使用`const`的建议:
### 1. 为什么使用 `const`
- **防止意外修改**:使用`const`可以防止变量被意外修改,从而保护数据的完整性。
- **提高代码可读性**:`const`明确表达了程序员的意图,即某个变量不应该被修改,从而提高代码的可读性。
- **优化编译器**:编译器可以利用`const`信息进行某些优化,从而可能提高程序的效率。
- **接口契约**:在函数参数中使用`const`可以明确指定哪些参数是只读的,这对于维护和理解代码有帮助。
### 2. 示例:不使用 `const`
如果不使用`const`,变量可以在任何时候被修改。下面是一个没有使用`const`的示例:
```cpp
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int a = 100;
int b = 200;
int result = add(a, b);
printf("Result: %d\n", result);
return 0;
}
```
在这个示例中,`a`和`b`可以在任何时候被修改,因为它们不是常量。
### 3. 示例:使用 `const`
通过在适当的地方使用`const`,我们可以提高代码的安全性和可读性:
```cpp
#include <stdio.h>
int add(const int a, const int b) {
return a + b;
}
int main() {
const int a = 100;
const int b = 200;
int result = add(a, b);
printf("Result: %d\n", result);
return 0;
}
```
在这个示例中,`const`关键字表明变量`a`和`b`在整个程序的生命周期内不能被修改。
### 4. 使用 `const` 在指针中
`const`在指针中的使用有多种形式:
- **指向常量的指针**:
```cpp
const int* ptr = &a; // ptr是指向const int的指针,不能通过ptr修改a的值
```
- **常量指针**:
```cpp
int* const ptr = &a; // ptr是一个常量指针,不能修改ptr本身,但可以修改它指向的值
```
- **指向常量的常量指针**:
```cpp
const int* const ptr = &a; // ptr是一个指向const int的常量指针,既不能修改ptr,也不能修改它指向的值
```
### 5. 修改前面提到的文件操作示例以使用 `const`
在前面的文件操作示例中,我们可以选择性地使用`const`来提高代码的安全性和可读性。以下是一个包含`const`的改进版本:
```cpp
#include <stdio.h>
int main() {
// 文件名
const char* filename = "text.txt";
// 1. 打开文件以写模式,如果文件不存在将创建该文件
FILE* file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file for writing");
return 1;
}
// 写入名字到文件
const char* name = "Your Name"; // 将这里替换为你的名字
fprintf(file, "%s", name); // 将名字写入文件
fclose(file); // 关闭文件
// 2. 以读模式打开文件
file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file for reading");
return 1;
}
// 读取文件内容
char buffer[256]; // 缓冲区用于存储读取的文件内容
if (fgets(buffer, sizeof(buffer), file) != NULL) { // 读取文件内容
printf("Content of file: %s\n", buffer); // 打印文件内容
} else {
perror("Error reading file");
}
fclose(file); // 关闭文件
return 0;
}
```
在这个示例中,`filename`和`name`都被声明为`const`,因为它们在整个程序中都不需要被修改。这种做法提高了代码的安全性和可读性。
### 总结
虽然使用`const`不是必须的,但它在防止意外修改、提高代码可读性和可维护性方面具有重要作用。养成在合适的地方使用`const`的习惯,是良好的编程实践。
文件缓存区
什么时候用呢
可以自己设定缓存区linux对于文件都是普通文件,对于用户来说才有不同
了解如何控制缓存区